├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── WikiTreeAPI.js
├── css
├── wt-avatars.css
├── wt-buttons.css
├── wt-comments.css
├── wt-components.css
├── wt-content.css
├── wt-examples.css
├── wt-fonts.css
├── wt-footer.css
├── wt-forms.css
├── wt-header.css
├── wt-icons.css
├── wt-links.css
├── wt-navigation.css
├── wt-opensans.css
├── wt-privacy.css
├── wt-stylesheet.css
└── wt-tree.css
├── docs
├── codestyle.md
├── contributing.md
└── tutorial.md
├── icons
├── icon-privacy-closed-dark.svg
├── icon-privacy-closed-light.svg
└── icon-privacy-open.svg
├── index.html
├── index.js
├── lib
├── biocheck-api
│ ├── README.md
│ ├── biocheck-api.md
│ ├── package.json
│ ├── readme-base.md
│ └── src
│ │ ├── BioCheckPerson.js
│ │ ├── BioCheckTemplateManager.js
│ │ ├── Biography.js
│ │ └── SourceRules.js
└── utilities.js
├── person_name.js
├── tree.css
├── tree.js
└── views
├── ahnentafel
├── ahnentafel.css
└── ahnentafel.js
├── ancestorLines
├── ale.css
├── ale_view.js
├── ancestor_lines_explorer.js
├── ancestor_tree.js
├── api.js
├── display.js
├── jquery.floatingscroll.css
├── jquery.floatingscroll.es6.min.js
└── person.js
├── ancestorsCemeteries
└── ancestorsCemeteries.js
├── baseDynamicTree
└── WikiTreeDynamicTreeViewer.js
├── cc7
├── cc7View.js
├── css
│ └── cc7.css
├── images
│ ├── 50px-Remember_the_Children-26.png
│ ├── Home_icon.png
│ ├── RTC_-_Pictures-6.png
│ ├── Remember_the_Children-21.png
│ ├── Remember_the_Children-27.png
│ ├── aids-ribbon-icon.png
│ ├── baby_bricks_small.png
│ ├── blue_bricks_small.jpg
│ ├── butterfly-icon.png
│ ├── candle-light-color-icon.png
│ ├── candle-light-icon.png
│ ├── checkbox-cross-red-icon.png
│ ├── checkmark-box-green-icon.png
│ ├── diedYoung.png
│ ├── flower-plant-icon.png
│ ├── flower-rose-icon.png
│ ├── load-33_128.gif
│ ├── pink-and-blue-ribbon.png
│ ├── pink_bricks_small.jpg
│ ├── privacy_open.png
│ ├── privacy_privacy35.png
│ ├── privacy_private.png
│ ├── privacy_public-bio.png
│ ├── privacy_public-tree.png
│ ├── privacy_public.png
│ ├── privacy_unlisted.png
│ ├── purple_bricks_small.jpg
│ ├── question-mark-circle-outline-icon.png
│ ├── reverse.svg
│ ├── setting-icon.png
│ ├── spouse_bricks_small.png
│ ├── timeline.png
│ └── tree.gif
└── js
│ ├── CC7Notes.js
│ ├── CC7Utils.js
│ ├── CirclesView.js
│ ├── FileSaver.js
│ ├── FileSaver.min.js
│ ├── FileSaver.min.js.map
│ ├── HierarchyView.js
│ ├── IndexedDBHelper.js
│ ├── LanceView.js
│ ├── MissingLinksView.js
│ ├── PeopleTable.js
│ ├── Settings.js
│ ├── StatsView.js
│ ├── cc7.js
│ ├── date.format.js
│ ├── jquery-ui.js
│ ├── jquery-ui.min.js
│ ├── relationshipWorker.js
│ └── xlsx.full.min.js
├── compactCouplesTree
├── cct.css
├── cct_explorer.js
├── cct_view.js
├── couple.js
└── display.js
├── couplesTree
├── cache_loader.js
├── cached_person.js
├── couples_tree.css
├── couples_tree.js
└── people_cache.js
├── descendants
├── descendants.css
└── descendants.js
├── familyCalendar
├── calendar.css
├── familyCalendar.js
└── version.md
├── familyGroupApp
├── familyGroupApp.css
├── familyGroupApp.js
├── images
│ ├── print50.png
│ └── tree.gif
└── js
│ ├── collapse.js
│ ├── handleDates.js
│ ├── handleLinks.js
│ └── personDataCache.js
├── familyPortraits
├── portraits.css
└── portraits.js
├── familyView
├── familyView.css
└── familyView.js
├── fanChart
├── AhnenTafel.js
├── FanChartView.js
├── PeopleCollection.js
├── SVGfunctions.js
├── SettingsOptions.js
├── WTapps_Utils.js
└── personPopup.js
├── fandoku
├── FandokuView.js
├── crowdyayapplause25ppllong-6948.mp3
├── crowdyayapplause25ppllong-6948.ogg
├── ding-idea-40142.mp3
├── ding-idea-40142.ogg
├── ok.dat
├── small-applause-6695.mp3
└── small-applause-6695.ogg
├── fractalTree
└── FractalView.js
├── heritage
├── heritage.css
└── heritage.js
├── nameTest
└── name_test.js
├── oneNameTrees
├── category_mappings.js
├── d3dataformatter.js
├── familytreestatistics.js
├── images
│ ├── Native_American.png
│ ├── USBH.png
│ ├── adopted.png
│ └── tree.gif
├── location_data.js
├── locationstatistics.js
├── oneNameTrees.css
├── oneNameTrees.js
└── test.js
├── printerFriendly
├── PrinterFriendlyView.js
└── printerFriendly.css
├── shared
├── PDFs.js
└── Utils.js
├── slippyTree
├── index.html
├── resources
│ ├── copy.svg
│ ├── family.svg
│ ├── fullscreen.svg
│ ├── mouse.svg
│ ├── nonfullscreen.svg
│ ├── person.svg
│ └── trackpad.svg
├── script.js
└── style.css
├── stats
├── sortable-theme-slick.css
├── sortable.min.js
├── stats.css
└── stats.js
├── superBigFamTree
└── SuperBigFamView.js
├── surnames
├── surnames.css
└── surnames.js
├── timeline
└── TimelineView.js
├── webs
├── WebsView.js
├── websSingleMRCA-T.png
└── websSingleMRCA-V.png
├── wtPlusMaps
└── wtPlusMaps.js
└── xTree
└── XTreeView.js
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .vscode/sftp.json
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": false,
4 | "semi": true,
5 | "singleQuote": false,
6 | "quoteProps": "consistent",
7 | "trailingComma": "es5",
8 | "bracketSameLine": true,
9 | "bracketSpacing": true,
10 | "arrowParens": "always",
11 | "singleAttributePerLine": false,
12 | "printWidth": 120,
13 | "overrides": [
14 | {
15 | "files": ["**/*.html","**/*.css"],
16 | "options": {
17 | "tabWidth": 2
18 | }
19 | },
20 | {
21 | "files": ".prettierrc",
22 | "options": { "parser": "json" }
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | info@wikitree.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022 Interesting.com Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wikitree-dynamic-tree
2 |
3 | This app displays a family tree starting from one person profile at WikiTree. The display can be panned and zoomed, as well as expanded to include more ancestors or descendants. The [WikiTree API](https://github.com/wikitree/wikitree-api) is used to gather the profile data. The [D3.js](https://d3js.org/) library is used to draw the graph.
4 |
5 | ## Prerequisites
6 |
7 | - [WikiTree API](https://github.com/wikitree/wikitree-api)
8 | - [D3.js](https://d3js.org/)
9 | - [jQuery](https://jquery.com/)
10 |
11 | ## Usage
12 |
13 | The index page sets up a basic control container where the user can provide a starting WikiTree Person ID and select a tree view to use. There is also a button the user can click to sign in to the API (required to view content on non-public profiles).
14 |
15 | Once there is a starting profile id (either provided in the form or taken from the API login) and a view is selected (defaulting to the WikiTree Dynamic Tree), the view is drawn in a container.
16 |
17 | The dynamic tree can be zoomed and panned with the mouse. Clicking on a plus-sign expands the tree by loading additional ancestors or descendants. Clicking a node displays a pop-up with additional profile information.
18 |
19 | A new tree can be displayed by entering a new WikiTree ID in the form and clicking "go".
20 |
21 | ## Notes
22 |
23 | ### [tree.js](tree.js)
24 |
25 | This is the scaffolding code to set things up on page load and launch the appropriate tree view when a new one is selected or a new starting profile is provided.
26 |
27 | Cookies are used to store the API login id (if there is one), the starting profile id, and the selected view. Those are used as defaults when the page reloads.
28 |
29 | ### [WikiTreeDynamicTree.js](views/baseDynamicTree/WikiTreeDynamicTreeViewer.js)
30 |
31 | This contains the code specific to drawing the WikiTree Dynamic Tree. It uses D3.js for the rendering and code from TreeAPI.js to pull data from the API.
32 |
33 | ### [restyledBaseExample.js](views/restyledBaseExample/restyledBaseExample.js)
34 |
35 | This contains the code specific to drawing the WikiTree Dynamic Tree. This is the same as the main version but with a different styling. The purpose is to show how new view types may be added.
36 |
37 | ### [WikiTreeAPI.js](WikiTreeAPI.js)
38 |
39 | Utility functions for getting Person data from the WikiTree API.
40 |
41 | ### [tree.css](tree.css)
42 |
43 | Style elements for the scaffolding and the dynamic-tree nodes.
44 |
45 | ## New Views
46 |
47 | The tree viewer can be extended with additional views. See [documentation](docs/contributing.md).
48 |
49 | ## Example
50 |
51 | A hosted version is at: http://apps.wikitree.com/apps/wikitree-dynamic-tree/
52 |
--------------------------------------------------------------------------------
/css/wt-avatars.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Avatars
3 | */
4 |
5 | /* default profile avatar, 48x48 */
6 | .person--avatar {
7 | background-position: 50% 20%;
8 | background-repeat: no-repeat;
9 | background-size: cover;
10 | border-radius: 50%;
11 | display: inline-block;
12 | height: 48px;
13 | margin: 0 5px 0 0;
14 | overflow: hidden;
15 | padding: 0;
16 | vertical-align: middle;
17 | width: 48px;
18 | }
19 |
20 | .person--avatar a {
21 | display: block;
22 | height: 100%;
23 | width: 100%;
24 | }
25 |
26 | .person--avatar img {
27 | min-height: 48px;
28 | width: auto;
29 | }
30 |
31 | /* add borders for m/f */
32 | .tree--person_m .person--avatar,
33 | .person--avatar.person--m,
34 | .person--avatar.person--male {
35 | border: 2px solid #AFAFCF;
36 | }
37 |
38 | .tree--person_f .person--avatar,
39 | .person--avatar.person--f,
40 | .person--avatar.person--femail {
41 | border: 2px solid #CFAFAF;
42 | }
43 |
44 | .person--avatar.person--u,
45 | .person--avatar.person--no-gender {
46 | border: 2px solid #AFDEAF;
47 | }
48 |
49 | /* large avatar, 75x75 */
50 | .avatar--75 {
51 | height: 75px;
52 | width: 75px;
53 | }
54 |
55 | .avatar--75 img {
56 | min-height: 75px;
57 | }
58 |
59 | /* person preview, 53x53 */
60 | .avatar--53 {
61 | height: 53px;
62 | width: 53px;
63 | }
64 |
65 | .avatar--53 img {
66 | min-height: 53px;
67 | }
68 |
69 | /* comment avatar, 40x40 */
70 | .avatar--40 {
71 | height: 40px;
72 | width: 40px;
73 | }
74 |
75 | .avatar--40 img {
76 | min-height: 40px;
77 | }
78 |
79 | /* custom avatar, 32x32 */
80 | .avatar--32 {
81 | height: 32px;
82 | width: 32px;
83 | }
84 |
85 | .avatar--32 img {
86 | min-height: 32px;
87 | }
88 |
89 | /* edit profile, 30x30 */
90 | .avatar--30 {
91 | height: 30px;
92 | width: 30px;
93 | }
94 |
95 | .avatar--30 img {
96 | min-height: 30px;
97 | }
98 |
99 | .profile--actions .avatar--30 {
100 | position: relative;
101 | top: -3px;
102 | }
103 |
104 | /* surname avatar, 24x24*/
105 | .avatar--24 {
106 | height: 24px;
107 | width: 24px;
108 | }
109 |
110 | .avatar--24 img {
111 | min-height: 24px;
112 | }
113 |
114 | .profiles .person--avatar {
115 | margin-left: 10px;
116 | }
117 |
118 |
119 | .background--gender-male { background-color: #F2F1FF; }
120 | .background--gender-female { background-color: #FFEEEE; }
121 | .background--gender-no-gender { background-color: #EEFFEE; }
122 |
123 |
124 |
--------------------------------------------------------------------------------
/css/wt-buttons.css:
--------------------------------------------------------------------------------
1 | /*
2 | * 2024 Buttons
3 | */
4 |
5 | .btn,
6 | span.TAG,
7 | .jump--links .showHideTree,
8 | .jump--links #showHideDescendants,
9 | .cat--links a {
10 | border: none;
11 | font-family: "Roboto Mono", monospace;
12 | font-optical-sizing: auto;
13 | font-weight: 500;
14 | font-size: 1rem;
15 | font-style: normal;
16 | padding: 0.375rem 0.75rem;
17 | text-decoration: none !important;
18 | }
19 |
20 | .btn-block {
21 | width: 100%;
22 | }
23 |
24 | .btn.mx-auto {
25 | display: block;
26 | }
27 |
28 | .btn-primary,
29 | a.btn-primary:link {
30 | background-color: #FCB815;
31 | color: #393A3C !important;
32 | padding: 10px 24px;
33 | }
34 |
35 | .btn-primary:hover {
36 | background-color: #ffe270 !important;
37 | color: #393A3C !important;
38 | }
39 |
40 | .btn-secondary,
41 | .btn-secondary:visited {
42 | background-color: #25422d;
43 | color: #fff !important;
44 | font-size: 1rem;
45 | padding: 8px 20px;
46 | }
47 |
48 | .btn-secondary:hover,
49 | .btn-secondary:visited:hover {
50 | background-color: #008000;
51 | color: #fff !important;
52 | }
53 |
54 | .btn-cancel {
55 | background-color: #fff;
56 | border: 2px solid #393A3C;
57 | color: #393A3C;
58 | padding: 8px 22px;
59 | }
60 |
61 | .btn-cancel:hover {
62 | background-color: #393A3C;
63 | color: #fff;
64 | }
65 |
66 | .btn-sm {
67 | font-size: 0.875rem;
68 | letter-spacing: 0.025rem;
69 | line-height: 1rem;
70 | padding: 6px 12px;
71 | }
72 |
73 | form .btn-sm {
74 | padding: 11px 12px;
75 | }
76 |
77 | .btn-pill,
78 | a.btn-pill:link,
79 | a.btn-pill:visited,
80 | .cat--links a:link {
81 | background-color: #fff;
82 | border: 2px solid #25422d;
83 | border-radius: 18px;
84 | color: #25422d !important;
85 | font-size: 1rem;
86 | }
87 |
88 | .btn-pill:hover,
89 | a.btn-pill:hover,
90 | .cat--links a:hover,
91 | .btn-pill:visited:hover,
92 | a.btn-pill:visited:hover,
93 | .cat--links a:visited:hover {
94 | background-color: #008000;
95 | border: 2px solid #008000;
96 | color: #fff !important;
97 | }
98 |
99 | .btn-pill:hover .icon--upload,
100 | a.btn-pill:hover .icon--upload,
101 | .cat--links a:hover .icon--upload {
102 | background-image: url('/images/icons/icon-upload-white.svg');
103 | }
104 |
105 | .btn-pill-sm,
106 | a.btn-pill-sm:link,
107 | a.btn-pill-sm:visited,
108 | .jump--links .showHideTree,
109 | .jump--links #showHideDescendants {
110 | background-color: #fff;
111 | border: 2px solid #008000;
112 | border-radius: 18px;
113 | color: #008000;
114 | font-size: 0.875rem;
115 | }
116 |
117 | .jump--links .showHideTree:hover,
118 | .jump--links #showHideDescendants:hover {
119 | background-color: #008000;
120 | border: 2px solid #008000;
121 | color: #fff;
122 | cursor: pointer;
123 | }
124 |
125 | .btn-tag,
126 | a.btn-tag:link,
127 | a.btn-tag:visited,
128 | span.TAG {
129 | background-color: #E1F0B4;
130 | border: none;
131 | border-radius: 6px;
132 | color: #25422d;
133 | font-size: 0.75rem;
134 | text-decoration: none;
135 | text-transform: uppercase;
136 | }
137 |
138 | .btn-tag:hover,
139 | a.btn-tag:hover,
140 | span.TAG:hover {
141 | background-color: #CBDD98;
142 | }
143 |
144 | .btn-pill-sm:visited,
145 | a.btn-pill-sm:visited {
146 | color: #008000 !important;
147 | }
148 |
149 | .btn-pill-sm:hover,
150 | a.btn-pill-sm:hover,
151 | .btn-pill-sm:visited:hover,
152 | a.btn-pill-sm:visited:hover {
153 | background-color: #e1f0b4;
154 | color: #008000 !important;
155 | }
156 |
157 | .btn-pill-sm [class*="icon--"] {
158 | position: relative;
159 | top: 2px;
160 | }
161 |
162 | .btn-med {
163 | padding-bottom: 6px;
164 | padding-top: 6px;
165 | }
166 |
167 | .copy--buttons {
168 | list-style: none;
169 | margin: 0;
170 | padding: 0;
171 | }
172 |
173 | .copy--buttons li {
174 | display: inline;
175 | margin: 0;
176 | padding: 0;
177 | }
178 |
179 | .copy--buttons li img {
180 | height: 18px;
181 | width: auto;
182 | }
183 |
184 | .copy--buttons button,
185 | .copy--buttons a {
186 | appearance: none;
187 | background-color: transparent;
188 | border: none;
189 | color: #008000;
190 | font-size: 0.875rem;
191 | text-decoration: underline;
192 | text-transform: uppercase;
193 | margin: 0;
194 | padding: 0;
195 | }
196 |
197 | .copy--buttons li.lifespan {
198 | font-family: "Roboto", sans-serif;
199 | font-weight: 700;
200 | font-size: 1.5rem;
201 | font-style: normal;
202 | margin-right: 10px;
203 | }
204 |
205 | .btn-utility {
206 | border-bottom: 1px solid #008000;
207 | border-radius: 0;
208 | color: #008000;
209 | font-size: 14px !important;
210 | line-height: 14px !important;
211 | margin: 0 10px 0 0;
212 | padding: 0;
213 | }
214 |
215 | .btn-utility:hover {
216 | background: transparent !important;
217 | border: none;
218 | color: #008000 !important;
219 | }
220 |
221 | a.btn-utility:visited {
222 | border-color: #800080;
223 | color: #800080;
224 | }
225 |
226 | .btn-icon {
227 | padding: 0;
228 | }
229 |
230 | /* disabled */
231 | .btn:disabled {
232 | background-color: #dedecb;
233 | color: #25422d;
234 | cursor: not-allowed;
235 | pointer-events: all !important;
236 | }
237 |
238 | .btn:disabled:hover {
239 | background-color: #dedecb !important;
240 | }
--------------------------------------------------------------------------------
/css/wt-comments.css:
--------------------------------------------------------------------------------
1 | /*
2 | ** 2024 Comments
3 | */
4 |
5 | #commentContainer .comment {
6 | background-color: rgba(240,240,235,0.5);
7 | border-radius: 12px;
8 | margin: 0 0 15px;
9 | padding: 15px;
10 | overflow-wrap: break-word;
11 | word-wrap: break-word;
12 | -ms-word-break: break-all;
13 | word-break: break-all;
14 | word-break: break-word;
15 | -ms-hyphens: auto;
16 | -moz-hyphens: auto;
17 | -webkit-hyphens: auto;
18 | hyphens: auto;
19 | }
20 |
21 | #commentContainer .comment-depth-1,
22 | #commentContainer .comment-depth-2,
23 | #commentContainer .comment-depth-3,
24 | #commentContainer .comment-depth-4,
25 | #commentContainer .comment-depth-5 {
26 | background-color: rgba(240,240,235,0.25);
27 | background-image: url('/images/icons/icon-reply.svg');
28 | background-position: 3px 15px;
29 | background-repeat: no-repeat;
30 | background-size: 25px 25px;
31 | padding-left: 30px;
32 | }
33 |
34 | #commentContainer .comment-depth-1 {
35 | margin-left: 30px;
36 | }
37 |
38 | #commentContainer .comment-depth-2 {
39 | margin-left: 60px;
40 | }
41 |
42 | #commentContainer .comment-depth-3 {
43 | margin-left: 90px;
44 | }
45 |
46 | #commentContainer .comment-depth-4 {
47 | margin-left: 120px;
48 | }
49 |
50 | #commentContainer .comment-depth-5 {
51 | margin-left: 150px;
52 | }
53 |
54 | .comment-header {
55 | margin-bottom: 1rem;
56 | position: relative;
57 | }
58 |
59 | .comment-header img.icon--archived {
60 | height: 20px;
61 | opacity: 0.75;
62 | position: absolute;
63 | right: 5px;
64 | top: 10px;
65 | width: 20px;
66 | }
67 |
68 | .comment-info p {
69 | margin: 1rem 0;
70 | }
71 |
72 | .comment-form-container {
73 | display: none;
74 | }
75 |
76 | .comment-hide {
77 | display: none !important;
78 | }
79 |
80 | /* memories */
81 | #memoryContainer .comment {
82 | background-color: rgba(240,240,235,0.5);
83 | border-radius: 12px;
84 | margin: 0 0 15px;
85 | padding: 15px;
86 | }
87 |
88 | #memoryContainer .icon--help {
89 | position: relative;
90 | top: 6px;
91 | }
92 |
93 | .comment-absent {
94 | font-size: 1rem;
95 | }
--------------------------------------------------------------------------------
/css/wt-examples.css:
--------------------------------------------------------------------------------
1 | /*
2 | 2024 Grid Examples
3 | */
4 |
5 | .container.example {
6 | padding-left: 15px;
7 | padding-right: 15px;
8 | }
9 |
10 | .container.example .row [class^="col"] {
11 | background-color: #f0f0eb;
12 | border: 1px solid #dedecb;
13 | padding: 1rem;
14 | }
15 |
16 | .container.example .row .row { margin-top: 1rem; }
17 |
18 | ul.colorblocks {
19 | clear: both;
20 | display: block;
21 | list-style: none;
22 | margin: 00;
23 | padding: 0;
24 | }
25 |
26 | ul.colorblocks li {
27 | display: inline-block;
28 | margin-bottom: 20px;
29 | margin-right: 10px;
30 | }
31 |
32 | ul.colorblocks li:last-child { margin-right: 0; }
33 |
34 | .colorblocks figure {
35 | display: block;
36 | height: 70px;
37 | margin: 0;
38 | width: 70px;
39 | }
40 |
41 | .colorblocks figcaption {
42 | font-family: "Roboto Mono", monospace;
43 | font-optical-sizing: auto;
44 | font-weight: 400;
45 | font-style: normal;
46 | font-size: 14px;
47 | }
--------------------------------------------------------------------------------
/css/wt-fonts.css:
--------------------------------------------------------------------------------
1 |
2 | /* roboto sans */
3 | .roboto-regular {
4 | font-family: "Roboto", sans-serif;
5 | font-weight: 400;
6 | font-style: normal;
7 | }
8 |
9 | .roboto-bold {
10 | font-family: "Roboto", sans-serif;
11 | font-weight: 700;
12 | font-style: normal;
13 | }
14 |
15 | .roboto-regular-italic {
16 | font-family: "Roboto", sans-serif;
17 | font-weight: 400;
18 | font-style: italic;
19 | }
20 |
21 | .roboto-bold-italic {
22 | font-family: "Roboto", sans-serif;
23 | font-weight: 700;
24 | font-style: italic;
25 | }
26 |
27 | /* roboto mono */
28 | .mono {
29 | font-family: "Roboto Mono", monospace;
30 | font-optical-sizing: auto;
31 | font-weight: 400;
32 | font-style: normal;
33 | }
34 |
35 | p.mono {
36 | font-size: 1.125rem;
37 | }
38 |
39 | .mono-m {
40 | font-family: "Roboto Mono", monospace;
41 | font-optical-sizing: auto;
42 | font-weight: 500;
43 | font-style: normal;
44 | }
45 |
46 | .mono-b {
47 | font-family: "Roboto Mono", monospace;
48 | font-optical-sizing: auto;
49 | font-weight: 700;
50 | font-style: normal;
51 | }
52 |
53 | /* open sans */
54 | .open-sans-reg {
55 | font-family: "Open Sans", sans-serif;
56 | font-optical-sizing: auto;
57 | font-weight: 400;
58 | font-style: normal;
59 | font-variation-settings:
60 | "wdth" 100;
61 | }
62 |
63 | .open-sans-bold {
64 | font-family: "Open Sans", sans-serif;
65 | font-optical-sizing: auto;
66 | font-weight: 700;
67 | font-style: normal;
68 | font-variation-settings:
69 | "wdth" 100;
70 | }
--------------------------------------------------------------------------------
/css/wt-footer.css:
--------------------------------------------------------------------------------
1 | /*
2 | * 2024 Footer
3 | */
4 |
5 | .connections,
6 | .category--links,
7 | #subfooter {
8 | background-color: rgba(240,240,235,0.5);
9 | }
10 |
11 | .category--links {
12 | padding-top: 1rem;
13 | }
14 |
15 | #subfooter hr {
16 | background-color: #25422D;
17 | border: none;
18 | height: 10px;
19 | margin-top: 0;
20 | opacity: 1;
21 | }
22 |
23 | #subfooter .list-inline-item { padding: 0 10px; }
24 |
25 | #subfooter p { margin: 0; }
26 |
27 | footer#footer {
28 | background-color: #25422d;
29 | color: #fff;
30 | font-size: 1rem;
31 | margin-top: 0;
32 | padding: 60px 0;
33 | }
34 |
35 | #footer a,
36 | #footer a:link {
37 | color: #fff !important;
38 | text-decoration: underline;
39 | }
40 |
41 | #footer a:hover {
42 | text-decoration: none;
43 | }
44 |
45 | footer#footer p { margin: 0; padding: 0; }
46 |
47 | footer#footer img {
48 | max-height: 80px;
49 | }
50 |
51 | #footer ul.nav.nav--col-2 {
52 | column-count: 2;
53 | column-gap: 20px;
54 | display: block;
55 | }
56 |
57 | #footer ul.nav .nav-item {
58 | margin-bottom: 0.5em;
59 | }
60 |
61 | #footer ul.nav a.nav-link {
62 | color: #fff;
63 | font-size: 1rem;
64 | padding: 0;
65 | text-decoration: underline;
66 | }
67 |
68 | #footer ul.nav a.nav-link:hover {
69 | color: #fff;
70 | text-decoration: none;
71 | }
72 |
73 | #footer ul.nav a.nav-link.active {
74 | background-color: transparent;
75 | color: #fff;
76 | }
77 |
78 | @media (max-width: 980px) {
79 | #footer ul.nav.nav--col-2 {
80 | column-count: 1;
81 | display: block;
82 | }
83 | }
--------------------------------------------------------------------------------
/css/wt-forms.css:
--------------------------------------------------------------------------------
1 | /*
2 | * 2024 Forms
3 | */
4 |
5 | form h2 {
6 | margin-top: 2.5rem;
7 | }
8 |
9 | form label {
10 | font-family: "Roboto", sans-serif;
11 | font-weight: 400;
12 | font-style: normal;
13 | font-size: 1.125rem;
14 | line-height: 1;
15 | }
16 |
17 | form label.required {
18 | font-weight: 700;
19 | }
20 |
21 | form .form-control {
22 | /* border-color: #25422D; */
23 | padding: 0.5rem 0.75rem;
24 | width: 100%;
25 | }
26 |
27 | form .form-control:focus,
28 | form .form-select:focus {
29 | border-color: #fad158;
30 | box-shadow: 0 0 0 0.25rem rgba(252, 181, 21, 0.25);
31 | }
32 |
33 | .input-group-text {
34 | background-color: #f0f0eb;
35 | padding: 0.375rem;
36 | }
37 |
38 | .input-group-text img,
39 | .input-group img,
40 | label img,
41 | form h2 img,
42 | .form-text img,
43 | .form-check img,
44 | form h3 img {
45 | max-height: 22px;
46 | }
47 |
48 | .form-select {
49 | font-family: "Roboto", sans-serif;
50 | font-weight: 400;
51 | font-style: normal;
52 | padding-top: 0.5rem;
53 | padding-bottom: 0.5rem;
54 | }
55 |
56 | .form-select-inline {
57 | display: inline-block;
58 | font-size: 0.875rem;
59 | width: auto;
60 | }
61 |
62 | textarea {
63 | width: 100%;
64 | }
65 |
66 | .form-text {
67 | color: #393a3c;
68 | }
69 |
70 | .form-text code {
71 | font-size: 0.875rem;
72 | }
73 |
74 | .form-control-plaintext {
75 | background-color: rgba(240, 240, 235, 0.35);
76 | border-radius: 0.375rem;
77 | padding: 0.5rem;
78 | position: relative;
79 | flex: 1 1 auto;
80 | width: 1%;
81 | min-width: 0;
82 | }
83 |
84 | .form-control-plaintext + .input-group-text {
85 | background-color: rgba(240, 240, 235, 0.35);
86 | border: none;
87 | }
88 |
89 | /* inline form check */
90 | form .form-check {
91 | margin: 0;
92 | padding: 0.25rem 0 0;
93 | }
94 |
95 | form .form-check .form-check-label,
96 | form .form-check label {
97 | font-size: 1rem;
98 | font-weight: 400;
99 | padding-right: 10px;
100 | }
101 | form .form-check .form-check-label-sm, form .form-check-label-sm {
102 | font-weight: 400;
103 | padding-right: 10px;
104 | font-size: 0.875rem;
105 | }
106 |
107 | form .form-check input.form-check-input,
108 | form .form-check input[type="radio"],
109 | form input[type="radio"],
110 | form .form-check input[type="checkbox"],
111 | form input[type="checkbox"] {
112 | border-color: #ccc;
113 | border-width: 1px;
114 | float: none;
115 | font-size: 1rem;
116 | margin: 6px 2px 0 0;
117 | }
118 |
119 | form .form-check-sm input.form-check-input-sm,
120 | form .form-check-sm
121 | form .form-check-sm {
122 | border-color: #ccc;
123 | border-width: 1px;
124 | float: none;
125 | font-size: 0.875rem;
126 | margin: 6px 2px 0 0;
127 | }
128 |
129 |
130 | form .form-check .form-check-input:focus,
131 | form .form-check input[type="radio"]:focus,
132 | form input[type="radio"]:focus,
133 | form .form-check input[type="checkbox"]:focus,
134 | form input[type="checkbox"]:focus {
135 | border-color: #25422d;
136 | box-shadow: 0 0 0 0.25rem rgba(0, 128, 0, 0.25);
137 | }
138 |
139 | form .form-check .form-check-input:checked,
140 | form .form-check input[type="radio"]:checked,
141 | form input[type="radio"]:checked,
142 | form input[type="checkbox"]:checked {
143 | background-color: #25422d;
144 | border-color: #25422d;
145 | }
146 |
147 | form .form-check .form-check-label .form-check-input {
148 | margin-right: 6px;
149 | margin-top: 0;
150 | }
151 |
152 | form .form-check-input:disabled,
153 | form input[type="radio"]:disabled,
154 | form input[type="checkbox"]:disabled {
155 | display: none;
156 | }
157 |
158 | form label input,
159 | form label.form-check-label input.form-check-input {
160 | margin-top: 1px;
161 | }
162 |
163 | /* edit page overwrites */
164 | .edit--sidebar a {
165 | font-size: 1rem;
166 | }
167 |
168 | .edit--sidebar p a {
169 | font-size: 1.125rem;
170 | }
171 |
172 | .page--content h2 a,
173 | .edit--sidebar h2 a {
174 | position: relative;
175 | top: -4px;
176 | }
177 |
178 | .edit--sidebar .form-check-label {
179 | padding-right: 4px !important;
180 | }
181 |
182 | .edit--sidebar .form-check img,
183 | .edit--sidebar label img {
184 | max-height: 20px;
185 | }
186 |
187 | .edit--sidebar ol {
188 | margin-bottom: 0.5rem;
189 | }
190 |
191 | .edit--sidebar ol li {
192 | font-size: 1rem;
193 | }
194 |
195 | .edit--sidebar .tree--person::after {
196 | display: none;
197 | }
198 |
199 | .edit--sidebar .p-gender {
200 | border-radius: 4px;
201 | display: inline-block;
202 | height: 16px;
203 | position: relative;
204 | top: 2px;
205 | width: 16px;
206 | }
207 |
208 | .edit--sidebar .photo--wrapper {
209 | position: sticky;
210 | top: 1vh;
211 | }
212 |
213 | /* tree overwrites */
214 | form .tree--person {
215 | padding: 8px 10px;
216 | }
217 |
218 | /* suggestions */
219 | .suggestion-item {
220 | border-radius: 0.375rem;
221 | color: #25422d;
222 | padding: 15px;
223 | }
224 |
225 | .suggestion-item .form-control {
226 | border-color: #666;
227 | }
228 |
229 | .suggestion-item img {
230 | max-height: 20px;
231 | }
232 |
233 | .suggestion-item.suggestion-Error {
234 | background-color: #ffcccc;
235 | }
236 |
237 | .suggestion-item.suggestion-Warning {
238 | background-color: #ffee99;
239 | }
240 |
241 | .suggestion-item.suggestion-Hint {
242 | background-color: #e1f0b4;
243 | }
244 |
--------------------------------------------------------------------------------
/css/wt-header.css:
--------------------------------------------------------------------------------
1 | /*
2 | ** 2024 Header
3 | */
4 |
5 | header {
6 | background-color: #F0F0EB;
7 | /* border-top: 5px solid #25422D; */
8 | color: #25422D;
9 | padding: 16px 0;
10 | width: 100%;
11 | }
12 |
13 | header img {
14 | max-height: 40px;
15 | max-width: 100%;
16 | }
17 |
18 | header .btn-search img {
19 | height: 30px;
20 | -webkit-transform: scaleX(-1);
21 | transform: scaleX(-1);
22 | transition: all 250ms ease-in-out;
23 | width: 30px;
24 | }
25 |
26 | header .btn-search:hover img {
27 | opacity: 0.5;
28 | }
29 |
30 | /* banner */
31 | #banner {
32 | background-color: #25422D;
33 | color: #fff;
34 | font-family: "Roboto Mono", monospace;
35 | font-optical-sizing: auto;
36 | font-weight: 400;
37 | font-style: normal;
38 | font-size: 0.875rem;
39 | padding: 5px 0;
40 | display:none;
41 | }
42 |
43 | /* mobile styles */
44 | @media (max-width: 980px) {
45 | header {
46 | padding-bottom: 16px;
47 | }
48 |
49 | header .search img {
50 | top: 9px;
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/css/wt-links.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Icon Links
3 | */
4 |
5 | #matches *[class*="icon--"] {
6 | display: inline-block;
7 | margin-left: 2px;
8 | }
9 |
10 | *[class*="icon--"] {
11 | appearance: none;
12 | border: 0;
13 | display: inline-block;
14 | padding: 0;
15 | text-indent: -9999em;
16 | }
17 |
18 | .icon--help {
19 | background: transparent url('/images/icons/icon-question.svg') 0 0 no-repeat;
20 | background-size: 26px 26px;
21 | height: 26px;
22 | width: 26px;
23 | }
24 |
25 | .icon--back {
26 | background: transparent url('/images/icons/icon-back.svg') 0 0 no-repeat;
27 | background-size: 20px 20px;
28 | height: 20px;
29 | width: 20px;
30 | }
31 |
32 | .icon--close {
33 | background: transparent url('/images/icons/icon-close.svg') 0 0 no-repeat;
34 | background-size: 26px 26px;
35 | height: 26px;
36 | width: 26px;
37 | }
--------------------------------------------------------------------------------
/css/wt-navigation.css:
--------------------------------------------------------------------------------
1 | /*
2 | * 2024 Navs
3 | */
4 |
5 | .btn-link {
6 | color: #25422d !important;
7 | font-family: "Roboto", sans-serif;
8 | font-weight: 400;
9 | font-style: normal;
10 | font-size: 1.125rem;
11 | }
12 |
13 | .dropdown-toggle:hover {
14 | color: #008000;
15 | }
16 |
17 | .dropdown-menu {
18 | z-index: 9999;
19 | }
20 |
21 | .dropdown-menu.show {
22 | border-color: #666;
23 | }
24 |
25 | .dropdown-menu li {
26 | margin-bottom: 0.125rem;
27 | }
28 |
29 | .dropdown-item:hover,
30 | .dropdown-item:focus {
31 | background-color: rgba(255,238,153,0.3);
32 | }
33 |
34 | /* jump links */
35 | ul.jump--links li {
36 | display: inline;
37 | margin-right: 15px;
38 | }
39 |
40 | ul.jump--links li:last-child { margin-right: 0; }
41 |
42 | /* nav links */
43 | ul.nav a.nav-link {
44 | color: #25422D;
45 | font-size: 1.125rem;
46 | text-decoration: none;
47 | }
48 |
49 | ul.nav a.nav-link:hover {
50 | color: #008000;
51 | }
52 |
53 | ul.nav a.nav-link.active {
54 | background-color: transparent;
55 | color: #25422D;
56 | }
57 |
58 | /* nav pills */
59 | .nav.nav-pills {
60 | margin-left: 0 !important;
61 | }
62 |
63 | .nav.nav-pills .nav-link {
64 | background-color: #F0F0EB;
65 | color: #008000 !important;
66 | font-size: 1rem;
67 | }
68 |
69 | .nav.nav-pills .nav-link.active {
70 | background-color: #008000;
71 | color: #fff !important;
72 | }
73 |
74 | .nav.nav-pills .nav-item {
75 | margin-right: 0.5rem;
76 | }
77 |
78 | .nav.nav-pills .nav-item:last-child {
79 | margin-right: 0;
80 | }
81 |
82 | /* vertical nav */
83 |
84 | ul.nav.flex-column .nav-item {
85 | padding-left: 1rem;
86 | margin-bottom: 1rem;
87 | }
88 |
89 | ul.nav.flex-column .nav-link {
90 | color: #008000;
91 | padding: 0;
92 | }
93 |
94 | ul.nav.flex-column .nav-link.active {
95 | font-weight: bold;
96 | }
97 |
98 | /* category nav */
99 | #main .category--links,
100 | #content + .category--links {
101 | background-color: transparent;
102 | border-bottom: 2px solid #F0F0EB;
103 | border-top: 2px solid #F0F0EB;
104 | font-size: 1rem;
105 | }
106 |
107 | /* mobile styles */
108 | @media (max-width: 980px) {
109 |
110 | .btn-search {
111 | padding-right: 0;
112 | }
113 |
114 | .btn-close {
115 | background-image: url('/images/icons/icon-plus.svg');
116 | background-size: 40px 40px;
117 | opacity: 1;
118 | transform: rotate(45deg);
119 | }
120 |
121 | .offcanvas-body {
122 | padding: 0;
123 | }
124 |
125 | .btn-group {
126 | border-bottom: 1px solid #F0F0EB;
127 | border-radius: 0;
128 | display: block;
129 | padding-bottom: 10px;
130 | padding-top: 10px;
131 | position: relative;
132 | width: 100%;
133 | }
134 |
135 | .btn-logout {
136 | border-bottom: none;
137 | }
138 |
139 | .btn-group .btn {
140 | text-align: left;
141 | width: 100%;
142 | }
143 |
144 | .dropdown-toggle { position: relative; }
145 |
146 | .dropdown-toggle::after {
147 | position: absolute;
148 | top: 16px;
149 | right: 15px;
150 | }
151 |
152 | ul.dropdown-menu {
153 | border: none;
154 | width: 100%;
155 | }
156 |
157 | }
--------------------------------------------------------------------------------
/css/wt-opensans.css:
--------------------------------------------------------------------------------
1 |
2 | .open-sans h1,
3 | .open-sans h2,
4 | .open-sans h3,
5 | .open-sans h4,
6 | .open-sans h5,
7 | .open-sans h6 {
8 | font-family: "Open Sans", sans-serif;
9 | font-optical-sizing: auto;
10 | font-weight: 700;
11 | font-style: normal;
12 | font-variation-settings:
13 | "wdth" 100;
14 | margin-bottom: 1.5rem;
15 | }
16 |
17 | .open-sans p,
18 | .open-sans ul,
19 | .open-sans ol {
20 | font-family: "Open Sans", sans-serif;
21 | font-optical-sizing: auto;
22 | font-weight: 400;
23 | font-style: normal;
24 | font-variation-settings:
25 | "wdth" 100;
26 | }
27 |
28 | .open-sans b,
29 | .open-sans strong {
30 | font-family: "Open Sans", sans-serif;
31 | font-optical-sizing: auto;
32 | font-weight: 700;
33 | font-style: normal;
34 | font-variation-settings:
35 | "wdth" 100;
36 | }
37 |
38 | .open-sans i,
39 | .open-sans em {
40 | font-family: "Open Sans", sans-serif;
41 | font-optical-sizing: auto;
42 | font-weight: 400;
43 | font-style: italic;
44 | font-variation-settings:
45 | "wdth" 100;
46 | }
--------------------------------------------------------------------------------
/css/wt-privacy.css:
--------------------------------------------------------------------------------
1 | /*
2 | Privacy Icons
3 | */
4 |
5 | p span[class*="privacy--"],
6 | ul:not(.profile--actions, .icons) li span[class*="privacy--"],
7 | ol:not(.profile--actions, .icons) li span[class*="privacy--"],
8 | table span[class*="privacy--"],
9 | .privacy--inline {
10 | background-size: 15px 15px !important;
11 |
12 | display: inline-block !important;
13 |
14 | height: 15px !important;
15 |
16 | position: relative !important;
17 |
18 | top: 2px !important;
19 |
20 | width: 15px !important;
21 | }
22 |
23 | .privacy {
24 | background-position: 50% 50%;
25 | background-size: 30px 30px;
26 | background-repeat: no-repeat;
27 | border-radius: 50%;
28 | display: block;
29 | }
30 |
31 | .privacy--60 {
32 | background-color: #fff;
33 | border: 2px solid #25422d;
34 | }
35 |
36 | .privacy--50 {
37 | background-color: #8fc641;
38 | }
39 |
40 | .privacy--40 {
41 | background-color: #ffe270;
42 | }
43 |
44 | .privacy--35 {
45 | background-color: #fad158;
46 | }
47 |
48 | .privacy--30 {
49 | background-color: #fcb815;
50 | }
51 |
52 | .privacy--20 {
53 | background-color: #cc0000;
54 | }
55 |
56 | .privacy--10 {
57 | background-color: #25422d;
58 | }
59 |
60 | /* open */
61 | .privacy--60,
62 | .privacy--50 {
63 | background-image: url("/icons/icon-privacy-open.svg");
64 | }
65 |
66 | /* private */
67 | .privacy--40,
68 | .privacy--35,
69 | .privacy--30 {
70 | background-image: url("/icons/icon-privacy-closed-dark.svg");
71 | }
72 |
73 | /* closed */
74 | .privacy--20,
75 | .privacy--10 {
76 | background-image: url("/icons/icon-privacy-closed-light.svg");
77 | }
78 |
79 | .privacy--sm {
80 | background-image: none;
81 | height: 15px !important;
82 | width: 15px !important;
83 | }
84 |
85 | .privacy--sm.privacy--60 {
86 | border-width: 1px;
87 | height: 15px !important;
88 | width: 15px !important;
89 | }
90 |
91 | /* .privacy--sm.privacy--60 {
92 | display: none;
93 | } */
94 |
95 | .privacy--md {
96 | background-size: 14px 14px;
97 | height: 26px !important;
98 | width: 26px !important;
99 | }
100 |
101 | .privacy--md.privacy--60 {
102 | border-width: 1px;
103 | }
104 |
105 | .privacy--lg,
106 | p span.privacy--lg,
107 | ul li span.privacy--lg,
108 | ol li span.privacy--lg,
109 | table span.privacy--lg {
110 | background-size: 30px 30px !important;
111 | height: 50px !important;
112 | margin-left: 5px;
113 | width: 50px !important;
114 | }
115 |
116 | @media (max-width: 980px) {
117 | .privacy--lg {
118 | background-size: 16px 16px !important;
119 | border-width: 1px;
120 | height: 26px !important;
121 | width: 26px !important;
122 | }
123 | }
124 |
125 | ul li.BULLET10,
126 | ul li.BULLET20,
127 | ul li.BULLET30,
128 | ul li.BULLET35,
129 | ul li.BULLET40,
130 | ul li.BULLET50,
131 | ul li.BULLET60 {
132 | list-style: none;
133 | padding-left: 20px;
134 | position: relative;
135 | }
136 |
137 | ul li.BULLET10::before,
138 | ul li.BULLET20::before,
139 | ul li.BULLET30::before,
140 | ul li.BULLET35::before,
141 | ul li.BULLET40::before,
142 | ul li.BULLET50::before,
143 | ul li.BULLET60::before {
144 | background-color: transparent;
145 | border-radius: 50%;
146 | content: "";
147 | display: block;
148 | height: 12px;
149 | left: 0;
150 | position: absolute;
151 | top: 8px;
152 | width: 12px;
153 | }
154 |
155 | ul li.BULLET10::before {
156 | background-color: #25422d;
157 | }
158 | ul li.BULLET20::before {
159 | background-color: #cc0000;
160 | }
161 | ul li.BULLET30::before {
162 | background-color: #fcb815;
163 | }
164 | ul li.BULLET35::before {
165 | background-color: #fad158;
166 | }
167 | ul li.BULLET40::before {
168 | background-color: #ffe270;
169 | }
170 | ul li.BULLET50::before {
171 | background-color: #8fc641;
172 | }
173 | ul li.BULLET60::before {
174 | background-color: #fff;
175 | border: 1px solid #25422d;
176 | height: 12px;
177 | width: 12px;
178 | }
179 |
180 | form .privacy {
181 | border-radius: 50%;
182 | display: inline-block;
183 | height: 12px;
184 | margin-right: 3px;
185 | position: relative;
186 | top: -2px;
187 | width: 12px;
188 | }
189 |
190 | form .footnote .privacy {
191 | top: 0;
192 | margin-left: 3px;
193 | }
194 |
195 | form .privacy.public,
196 | form .privacy.privacy-50 {
197 | background-color: #8fc641;
198 | }
199 | form .privacy.semiprivate,
200 | form .privacy.privacy-40 {
201 | background-color: #fad158;
202 | }
203 | form .privacy.private,
204 | form .privacy.privacy-20 {
205 | background-color: #cc0000;
206 | }
207 |
--------------------------------------------------------------------------------
/docs/codestyle.md:
--------------------------------------------------------------------------------
1 | # WikiTree code style
2 |
3 | ## Code style
4 |
5 | we aim for consistency, generality, readability and reduction of git diffs. Similar language constructs are formatted with similar rules. Style configuration options are deliberately limited and rarely added. Previous formatting is taken into account as little as possible.
6 |
7 | ## Basic rules
8 |
9 | | Rule | Value | Note |
10 | | --- | :---: | --- |
11 | | Line length | 120 characters | |
12 | | Indentation | 4 spaces | |
13 | | Semicolons at the end of the line| yes | |
14 | | Quote type | Double quotes | e.g. `myObj["propA"]` |
15 | | Trailing comma | optional | *If you want to add a new property, you can add a new line without modifying the previously last line if that line already uses a trailing comma. This makes version-control diffs cleaner and editing code might be less troublesome.* [[source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas)] |
16 | | Bracket spacing | yes | e.g. `function xyz(arg1){ console.log(arg1) }` |
17 | | Bracket line | yes | Put the > of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being alone on the next line (does not apply to self closing elements). |
18 | | Arrow Function Parentheses | yes | Arrow functions can omit parentheses when they have exactly one parameter. In all other cases the parameter(s) must be wrapped in parentheses. This rule enforces the consistent use of parentheses in arrow functions. [[source](https://eslint.org/docs/latest/rules/arrow-parens)] |
19 | | Single Attribute Per Line | no | Enforce single attribute per line in HTML |
20 |
21 | ### Formatters
22 |
23 | | Formatter | configuration file | Homepage | Editor integration |
24 | | --- | --- | --- | --- |
25 | | Prettier | [`.prettierrc`](../.prettierrc) | [https://prettier.io/](https://prettier.io/) | [VSCode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode), [WebStorm](https://plugins.jetbrains.com/plugin/10456-prettier), [Sublime Text](https://packagecontrol.io/packages/JsPrettier) and lot of others... check the [official website](https://prettier.io/docs/en/editors.html)|
26 |
27 | ## Naming conventions
28 |
29 | | Object | Convention |
30 | | --- | --- |
31 | | Variables | `camel case` with lowercase first letter, e.g. `familyName` |
32 | | Booleans | We should use `is` or `has` as prefixes, e.g. `isDead`, `hasWife` |
33 | | Functions | Same as with variables + should use descriptive nouns and verbs as prefixes, e.g. `getName()`, `generateReport` |
34 | | Constants | Should be written in `upper snake case`, e.g. `DEFAULT_COUNT_OF_GENERATIONS` |
35 | | Classes | Should be written in `Pascal case`, e.g. `WikiTreePerson` |
36 | | Methods | Same as functions |
37 | | Files | File names must be all lowercase and may include underscores (_) or dashes (-), but no additional punctuation. Follow the convention that your project uses. [[source](https://google.github.io/styleguide/jsguide.html#file-name)] - most Unix-based servers are case sensitives, but this doesn't apply for windows servers.|
38 |
39 | ### Legend
40 |
41 | * `camel case` - a way to separate the words in a phrase by making the first letter of each word capitalized and not using spaces, e.g. `familyName`, `LastName`, ...
42 | * `upper snake case` - words are written in uppercase and separated with underscores, e.g. `DEFAULT_COUNT_OF_GENERATIONS`
43 | * `Pascal case` - same as `camel case`, but the first letter is capitalized, e.g. `WikiTreePerson`
44 |
45 | ### Conflicting names
46 |
47 | #### Problem
48 |
49 | > I noticed that both the FanChart view and the Ahnentafal view had an "Ahnentafel" class. I was concerned about there being more than one window.Ahnentafel. Both views seemed to work, but I was seeing some odd problems during integration into WikiTree.com and thought it wouldn't hurt to change this. I modified the class name in the Ahnentafel view (to be "AhnentafelAncestorList").
50 |
51 | #### Solutions
52 | 1. Agree on some naming conventintions that would prevent this, e.g. use of prefix
53 | 1. use `javascript modules` ([docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) & [Can I Use page](https://caniuse.com/?search=modules)) and expose to outer world only what is needed.
54 |
55 | ### Useful reading
56 |
57 | * inspired by: [10 JavaScript Naming Conventions Every Developer Should Know](https://www.syncfusion.com/blogs/post/10-javascript-naming-conventions-every-developer-should-know.aspx)
58 |
59 |
--------------------------------------------------------------------------------
/docs/tutorial.md:
--------------------------------------------------------------------------------
1 | # Creating a new Tree App example
2 |
3 | This tutorial will walk you through creating a simple Tree App.
4 |
5 | ## Prerequisites
6 |
7 | This tutorial assumes you have 2 things:
8 |
9 | - An account on [GitHub](https://github.com/).
10 | - A code editor and terminal access (this tutorial will be using [Visual Studio Code](https://code.visualstudio.com/)).
11 | - An account on the [WikiTree Apps server](https://www.wikitree.com/wiki/Help:WikiTree_Apps_Server).
12 |
13 | ## Forking the repository
14 |
15 | Sign into your GitHub account.
16 |
17 | Go to the [WikiTree Tree Apps Code Repository](https://github.com/wikitree/wikitree-dynamic-tree).
18 |
19 | Click the "Fork" button at the top right. This will take you to a page titled "Create a new fork". You can leave everything as default. Press the "Create fork" button.
20 |
21 | You should now have a copy of the repository saved to your GitHub account.
22 |
23 | When viewing your copy of the code on GitHub, notice the green button that says "Code". This is where you can download the code to work on locally.
24 |
25 | ## Downloading the code
26 |
27 | Upon opening Visual Studio Code, you should be brought to a "Welcome" page. If you don't see this page, you can access it by going to `Help > Welcome` in the menu.
28 |
29 | Under "Start", there is an option to "Clone Git Repository". When you click that, it will ask for a URL.
30 |
31 | Go to your code repository on GitHub, click the green "Code" button, and copy the HTTPS link that is shown. Enter that into VS Code and select "Clone from URL".
32 |
33 | Select where you would like to save the code on your computer.
34 |
35 | After it has been cloned, select "Open" in VS Code.
36 |
37 | You should now see the code files in the left-hand sidebar.
38 |
39 | You will want to open a terminal so you can run git commands. You can do this in VS Code by selecting `Terminal > New Terminal` in the menu.
40 |
41 | ## Testing Tree Apps
42 |
43 | You should now have a copy of the extension code on your computer.
44 |
45 | Because of issues with CORS, the easiest way to test the code is to upload it to your directory on the WikiTree Apps Server.
46 |
47 | See [Help:WikiTree Apps Server](https://www.wikitree.com/wiki/Help:WikiTree_Apps_Server) for instructions on how to upload code there.
48 |
49 | Double-check that the Tree Apps code is running without errors in your apps directory before moving on to the next step.
50 |
51 | ## Setting up a new feature
52 |
53 | The first thing you want to do before writing a new feature is to switch to a new branch in git. In the terminal, type `git checkout -b helloWorld`.
54 |
55 | Create a folder inside `views` called `helloWorld`. Inside that folder, create a file called `helloWorld.js`. This is where you will write your app code. We will come back to it in a bit.
56 |
57 | In `index.html`, link your script in the `
` section.
58 |
59 | ```html
60 |
61 | ```
62 |
63 | The last step is to register your view in `index.js`.
64 |
65 | Add your app to the `views` constant:
66 |
67 | ```js
68 | const views = {
69 | "couples": new CouplesTreeView(),
70 | ...
71 | "helloWorld": new HelloWorldView(),
72 | };
73 | ```
74 |
75 | ## Writing your feature code
76 |
77 | Open the `helloWorld.js` file you created earlier.
78 |
79 | Enter this code:
80 |
81 | ```js
82 | window.HelloWorldView = class HelloWorldView extends View {
83 | meta() {
84 | return {
85 | title: "Hello World",
86 | description: "A simple example app.",
87 | };
88 | }
89 |
90 | init(container_selector, person_id) {
91 | // TODO
92 | }
93 | };
94 | ```
95 |
96 | This simple app will grab the WikiTree ID that was entered, and say `Hello, WikiTree ID`.
97 |
98 | To do this, we want to:
99 |
100 | - Ask the API for the First Name of the given WikiTree ID: `const personData = await WikiTreeAPI.getPerson("helloWorld", person_id, ["FirstName"])`
101 | - Grab the first name from the returned data: `const name = personData["_data"]["FirstName"]`
102 | - Show "Hello" and the name in the container: `` document.querySelector(container_selector).innerText = `Hello, ${name}` ``
103 |
104 | Combining these, the final code in `helloWorld.js` should look like this:
105 |
106 | ```js
107 | window.HelloWorldView = class HelloWorldView extends View {
108 | meta() {
109 | return {
110 | title: "Hello World",
111 | description: "A simple example app.",
112 | };
113 | }
114 |
115 | async init(container_selector, person_id) {
116 | const personData = await WikiTreeAPI.getPerson("helloWorld", person_id, ["FirstName"]);
117 | const name = personData["_data"]["FirstName"];
118 | document.querySelector(container_selector).innerText = `Hello, ${name}`;
119 | }
120 | };
121 | ```
122 |
123 | If you save an upload the code to your directory on your apps account, you should now see an app in the dropdown titled "Hello World", which shows "Hello, Name" on the screen.
124 |
125 | ## Requesting to add your code to the shared repo
126 |
127 | Now that your feature is finished, you want that feature be added to the shared code repository on GitHub.
128 |
129 | In the terminal, type `git status`. This will show you which files have been changed.
130 |
131 | Since we want to include all the files that have been changed, type `git add .`.
132 |
133 | Now you want to commit the changes with a short message about what was changed. Type `git commit -m "Add Hello World app."`.
134 |
135 | Now you want to add those changes to your GitHub repository. Type `git push --set-upstream origin helloWorld`. This will upload the code to your repository under the branch "helloWorld".
136 |
137 | If you go to your GitHub repository, you can now access the "helloWorld" branch in the dropdown on the left.
138 |
139 | Once you are viewing that branch, you should see a section that says "This branch is X commits ahead of wikitree/wikitree-dynamic-tree:main." and a button that says "Contribute".
140 |
141 | Click the "Contribute" button, and you will be taken to a form where you can fill out more information about your app. If this were an actual app that should be added to the code, you would press the "Create pull request" button to request that the maintainers view your code and add it to Tree Apps. But it isn't necessary for this tutorial.
142 |
143 | ## Additional Info
144 |
145 | If you ran into any trouble during this tutorial, feel free to ask for help in the [WikiTree Discord](https://www.wikitree.com/wiki/Help:Discord), the [WikiTree Apps Project Google Group](https://groups.google.com/forum/#!forum/wikitreeapps), or in [G2G](https://www.wikitree.com/g2g/) with the `wt_apps` tag.
146 |
147 | You can also practice your GitHub skills by improving this tutorial!
148 |
--------------------------------------------------------------------------------
/icons/icon-privacy-closed-dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-privacy-closed-light.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-privacy-open.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * index.js
3 | *
4 | * This code runs in index.html. It sets up the Login Manager and View Registry for the Tree.
5 | * There's not much code here, and it was included directly in index.html. However for integration into the
6 | * WikiTree.com website, it was separated out.
7 | *
8 | * New Views are added here, in the View Registry below.
9 | *
10 | */
11 |
12 | var wtViewRegistry;
13 | window.addEventListener("DOMContentLoaded", (event) => {
14 | const loginManager = new LoginManager(
15 | WikiTreeAPI,
16 | (events = {
17 | onLoggedIn: (user) => {
18 | document.querySelector(
19 | "#wt-api-login"
20 | ).innerHTML = `Logged into Apps: ${user.name} (Logout)`;
21 | },
22 | onUnlogged: () => {
23 | document.querySelector("#wt-api-login").innerHTML = `
24 |
30 | `;
31 |
32 | if (typeof requireAppsLogin != "undefined" && requireAppsLogin) {
33 | $("#appsLoginForm").submit();
34 | }
35 | },
36 | })
37 | );
38 | $("body").on("click", ".apiLogout", function (e) {
39 | e.preventDefault();
40 | loginManager.logout();
41 | });
42 |
43 | // To add a new View, add a unique keyword with a value of the new View().
44 | // The default view is the first one (but this is only used if the user has
45 | // not used the page before, since the last view used is saved in a cookie
46 | // and used the next time the user goes to this page).
47 | // Note: the keyword is used as part of the URL to get to the app.
48 | const views = {
49 | "fanchart": new FanChartView(),
50 | "couples": new CouplesTreeView(),
51 | "cctree": new CCTView(),
52 | "wt-dynamic-tree": new WikiTreeDynamicTreeViewer(),
53 | "timeline": new TimelineView(),
54 | "fandoku": new FandokuView(),
55 | "fractal": new FractalView(),
56 | "ahnentafel": new AhnentafelView(),
57 | "surnames": new SurnamesView(),
58 | "webs": new WebsView(),
59 | "familygroup": new FamilyView(),
60 | "printer-friendly": new PrinterFriendlyView(WikiTreeAPI, 5),
61 | "calendar": new CalendarView(),
62 | "portraits": new PortraitView(),
63 | "nameTest": new NameTestView(),
64 | "cc7": new CC7View(),
65 | "ale": new ALEView(),
66 | "descendants": new DescendantsView(),
67 | "xtree": new XTreeView(),
68 | "familyGroupApp": new FamilyGroupAppView(),
69 | "superbig": new SuperBigFamView(),
70 | "slippyTree": new SlippyTree(),
71 | "stats": new StatsView(),
72 | "wtPlusMaps": new WtPlusMaps(),
73 | "oneNameTrees": new OneNameTrees(),
74 | "ancestorsCemeteries": new AncestorsCemeteriesView(),
75 | "heritage": new HeritageView(),
76 | };
77 |
78 | for (let key in views) {
79 | let meta = views[key]?.meta();
80 | if (meta?.disabled) {
81 | delete views[key];
82 | }
83 | }
84 |
85 | wtViewRegistry = new ViewRegistry(views, new SessionManager(WikiTreeAPI, loginManager));
86 | wtViewRegistry.render();
87 | });
88 |
89 | //// "superbig": new SuperBigFamView(),
90 |
--------------------------------------------------------------------------------
/lib/biocheck-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "biocheck-api",
3 | "version": "1.6.4",
4 | "description": "Bio Check API",
5 | "main": "index.js",
6 | "scripts": {
7 | "docs": "documentation build --markdown-toc-max-depth=2 src/*.js -f md > biocheck-api.md && cat readme-base.md biocheck-api.md > README.md"
8 | },
9 | "keywords": ["WikiTree", "BioCheck"],
10 | "author": "",
11 | "license": "MIT"
12 | }
13 |
--------------------------------------------------------------------------------
/lib/biocheck-api/readme-base.md:
--------------------------------------------------------------------------------
1 | # Bio Check
2 | Code to check a WikiTree biography
3 |
4 | ## Shared Code
5 | The following are **identical** classes found in the Bio Check app, in the
6 | WikiTree Browser Extension, and in the WikiTree Dynamic Tree. Please do not
7 | modify these classes to introduce items that are not available in those
8 | contexts.
9 | * Biography.js
10 | * BioCheckPerson.js
11 | * SourceRules.js
12 | The Bio Check app and WikiTree Dynamic Tree make use of
13 | * BioCheckTemplateManager
14 |
15 | Example use:
16 | ```
17 | import { BioCheckTemplateManager } from "./BioCheckTemplateManager";
18 | import { theSourceRules } from "./SourceRules.js";
19 | import { BioCheckPerson } from "./BioCheckPerson.js";
20 | import { Biography } from "./Biography.js";
21 |
22 | // initialization - just once
23 | let bioCheckTemplateManager = new BioCheckTemplateManager();
24 | bioCheckTemplateManager.load();
25 |
26 | // For each person. Get the bio text and dates to test
27 | // userId is the userId number, not the WikiTree-Id
28 | let thePerson = new BioCheckPerson();
29 | let canUseThis = thePerson.canUse(profileObj, openOnly, orphanOnly, ignorePre1500, userId);
30 | if (canUseThis) {
31 | let biography = new Biography(theSourceRules);
32 | biography.parse(bioString, thePerson, searchString);
33 | let isValid = biography.validate();
34 | }
35 |
36 | // now report from biography (use getters) as desired or just the boolean return
37 | // you might want to report any biography that is not valid from the validate()
38 | // method as well as any any biography that hasStyleIssues()
39 | ```
40 | ## API
41 |
--------------------------------------------------------------------------------
/lib/biocheck-api/src/BioCheckTemplateManager.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2025 Kathryn J Knight
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 |
24 | /* **********************************************************************************
25 | * ***************************** WARNING **************************************
26 | *
27 | * This class is used in the BioCheck app and in the WikiTree Dynamic Tree.
28 | * Ensure that any changes do not bring in components
29 | * that are not supported in each of these environments.
30 | *
31 | * The request must be done with no-store so that the request can be made from
32 | * either tree tools or apps. The response will be held by the requestor, so the data
33 | * will not be requested until the tree tools or app are restarted/reloaded.
34 | *
35 | * **********************************************************************************
36 | * ******************************************************************************** */
37 |
38 | import { theSourceRules } from "./SourceRules.js";
39 |
40 | /**
41 | * BioCheck Template Manager
42 | */
43 | export class BioCheckTemplateManager {
44 | constructor() {
45 | }
46 |
47 | /**
48 | * Load the template data from WikiTree Plus.
49 | * use this method to wait for the load to complete
50 | * Send the templates into theSourceRules, which is a singleton.
51 | * This only needs to be done once, at initialization.
52 | *
53 | * This can take some time. You can instead use loadPrep to get
54 | * a promise and then await loadTemplates using that promise if
55 | * there is other initialization to be performed.
56 | *
57 | * @throws error getting response
58 | */
59 | async load() {
60 | try {
61 | await this.#loadTemplates();
62 | } catch (e) {
63 | console.log('Error loading templates ' + e);
64 | throw('Error loading templates');
65 | }
66 | }
67 | /*
68 | * Do the actual load separately so we can wait for this synchronously
69 | * The problem with this is that it can take a long time. Can we instead
70 | * be asynchronous in WBE and TreeApps and wait before user clicks in the App?
71 | */
72 | async #loadTemplates() {
73 | try {
74 | let url = "https://plus.wikitree.com/chrome/templatesExp.json?appid=bioCheck";
75 | let fetchResponse = await fetch(url, { cache: 'no-store' });
76 | if (!fetchResponse.ok) {
77 | console.log("Error " + fetchResponse.status);
78 | throw('Error loading templates');
79 | } else {
80 | const jsonData = await fetchResponse.json();
81 | // since you cannot do async from theSourceRules constructor
82 | theSourceRules.loadTemplates(jsonData.templates);
83 | }
84 | return fetchResponse;
85 | } catch (e) {
86 | console.log('Error loading templates ' + e);
87 | throw('Error loading templates');
88 | }
89 | }
90 |
91 | /**
92 | * Send request to load templates from WT+
93 | * @return promise for use with loadTemplates
94 | * @throws error getting response
95 | */
96 | loadPrep() {
97 | try {
98 | let url = "https://plus.wikitree.com/chrome/templatesExp.json?appid=bioCheck";
99 | let p = fetch(url, { cache: 'no-store' });
100 | return p;
101 | } catch (e) {
102 | console.log('Error loading templates ' + e);
103 | throw('Error loading templates');
104 | }
105 | }
106 | /**
107 | * load Templates - wait for load to complete
108 | * @param p promise returned from loadPrep
109 | * @throws error getting response
110 | */
111 | async loadTemplates(p) {
112 | try {
113 | let fetchResponse = await p;
114 | if (!fetchResponse.ok) {
115 | console.log("Error " + fetchResponse.status);
116 | throw('Error loading templates');
117 | } else {
118 | const jsonData = await fetchResponse.json();
119 | // since you cannot do async from theSourceRules constructor
120 | theSourceRules.loadTemplates(jsonData.templates);
121 | }
122 | return fetchResponse;
123 | } catch (e) {
124 | console.log('Error loading templates ' + e);
125 | throw('Error loading templates');
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/lib/utilities.js:
--------------------------------------------------------------------------------
1 | export function spell(text) {
2 | const americanToBritishSpelling = {
3 | // A
4 | acknowledgment: "acknowledgement",
5 | acknowledgments: "acknowledgements",
6 | aging: "ageing",
7 | analog: "analogue",
8 | analyze: "analyse",
9 | analyzed: "analysed",
10 | analyzes: "analyses",
11 | analyzing: "analysing",
12 | anglicize: "anglicise",
13 | anglicized: "anglicised",
14 | anglicizes: "anglicises",
15 | anglicizing: "anglicising",
16 | anonymize: "anonymise",
17 | anonymized: "anonymised",
18 | anonymizes: "anonymises",
19 | anonymizing: "anonymising",
20 | apologize: "apologise",
21 | apologized: "apologised",
22 | apologizes: "apologises",
23 | apologizing: "apologising",
24 | arbor: "arbour",
25 | arbors: "arbours",
26 | ax: "axe",
27 |
28 | // B
29 | baptize: "baptise",
30 | baptized: "baptised",
31 | baptizes: "baptises",
32 | baptizing: "baptising",
33 | behavior: "behaviour",
34 | behaviors: "behaviours",
35 |
36 | // C
37 | catalog: "catalogue",
38 | catalogs: "catalogues",
39 | center: "centre",
40 | centers: "centres",
41 | color: "colour",
42 | colored: "coloured",
43 | colorful: "colourful",
44 | colorfully: "colourfully",
45 | coloring: "colouring",
46 | colors: "colours",
47 |
48 | // D
49 | dialog: "dialogue",
50 | dialogs: "dialogues",
51 | draft: "draught",
52 | drafts: "draughts",
53 | defense: "defence",
54 | defenses: "defences",
55 |
56 | // E
57 | enroll: "enrol",
58 | enrolled: "enrolled",
59 | enrolling: "enrolling",
60 | enrollment: "enrolment",
61 | enrollments: "enrolments",
62 | encyclopedia: "encyclopaedia",
63 | encyclopedias: "encyclopaedias",
64 | esophagus: "oesophagus",
65 | esthetic: "aesthetic",
66 |
67 | // F
68 | favor: "favour",
69 | favored: "favoured",
70 | favoring: "favouring",
71 | favors: "favours",
72 | fiber: "fibre",
73 | fibers: "fibres",
74 | fulfill: "fulfil",
75 | fulfilled: "fulfilled",
76 | fulfilling: "fulfilling",
77 | fulfillment: "fulfilment",
78 | fulfillments: "fulfilments",
79 |
80 | // G
81 | gray: "grey",
82 | grays: "greys",
83 |
84 | // H
85 | honor: "honour",
86 | honored: "honoured",
87 | honoring: "honouring",
88 | honors: "honours",
89 | humor: "humour",
90 | humored: "humoured",
91 | humoring: "humouring",
92 | humors: "humours",
93 |
94 | // I-J
95 | inquiry: "enquiry",
96 | inquiries: "enquiries",
97 | jewelry: "jewellery",
98 | judgment: "judgement",
99 | judgments: "judgements",
100 |
101 | // L
102 | labor: "labour",
103 | labors: "labours",
104 | license: "licence",
105 | licenses: "licences",
106 | liter: "litre",
107 | liters: "litres",
108 | luster: "lustre",
109 |
110 | // M
111 | marveled: "marvelled",
112 | marveling: "marvelling",
113 | meager: "meagre",
114 | modeled: "modelled",
115 | modeling: "modelling",
116 | models: "models",
117 | mold: "mould",
118 | molds: "moulds",
119 | mom: "mum",
120 | moms: "mums",
121 |
122 | // N
123 | neighbor: "neighbour",
124 | neighboring: "neighbouring",
125 | neighbors: "neighbours",
126 |
127 | // O
128 | organization: "organisation",
129 | organizations: "organisations",
130 | organize: "organise",
131 | organized: "organised",
132 | organizes: "organises",
133 | organizing: "organising",
134 |
135 | // P
136 | personalize: "personalise",
137 | personalized: "personalised",
138 | personalizes: "personalises",
139 | personalizing: "personalising",
140 | plow: "plough",
141 | plows: "ploughs",
142 | practicing: "practising",
143 | privatize: "privatise",
144 | privatized: "privatised",
145 | privatizes: "privatises",
146 | privatizing: "privatising",
147 |
148 | // R
149 | realization: "realisation",
150 | realizations: "realisations",
151 | realize: "realise",
152 | realized: "realised",
153 | realizes: "realises",
154 | realizing: "realising",
155 | recognize: "recognise",
156 | recognized: "recognised",
157 | recognizes: "recognises",
158 | recognizing: "recognising",
159 | rumor: "rumour",
160 | rumors: "rumours",
161 |
162 | // S
163 | saber: "sabre",
164 | sabers: "sabres",
165 | skillful: "skilful",
166 | skillfully: "skilfully",
167 | somber: "sombre",
168 | sulfur: "sulphur",
169 |
170 | // T
171 | theater: "theatre",
172 | theaters: "theatres",
173 |
174 | // Traveling
175 | traveled: "travelled",
176 | traveler: "traveller",
177 | travelers: "travellers",
178 | traveling: "travelling",
179 |
180 | // V
181 | valor: "valour",
182 | vapor: "vapour",
183 | vapors: "vapours",
184 |
185 | // W
186 | willful: "wilful",
187 | willfully: "wilfully",
188 |
189 | // Add more as needed
190 | };
191 |
192 | const userLanguage = navigator.language || navigator.userLanguage;
193 | const useBritishEnglish = ["en-GB", "en-AU", "en-NZ", "en-ZA", "en-IE", "en-IN", "en-SG", "en-MT"].includes(
194 | userLanguage
195 | );
196 |
197 | function matchCase(original, transformed) {
198 | if (original === original.toUpperCase()) {
199 | return transformed.toUpperCase();
200 | }
201 | if (original[0] === original[0].toUpperCase()) {
202 | return transformed[0].toUpperCase() + transformed.slice(1);
203 | }
204 | return transformed;
205 | }
206 |
207 | return text
208 | .split(/\b/)
209 | .map((word) => {
210 | const lowerCaseWord = word.toLowerCase();
211 |
212 | if (americanToBritishSpelling.hasOwnProperty(lowerCaseWord)) {
213 | const converted = americanToBritishSpelling[lowerCaseWord];
214 | return useBritishEnglish ? matchCase(word, converted) : word;
215 | }
216 |
217 | return word;
218 | })
219 | .join("");
220 | }
221 |
222 | // This function returns a promise that resolves when the specified element is added to the DOM
223 | export function waitForElement(selector) {
224 | return new Promise((resolve, reject) => {
225 | const observer = new MutationObserver((mutations, obs) => {
226 | const element = document.querySelector(selector);
227 | if (element) {
228 | resolve(element);
229 | obs.disconnect();
230 | }
231 | });
232 | observer.observe(document.body, {
233 | childList: true,
234 | subtree: true,
235 | });
236 | });
237 | }
238 |
--------------------------------------------------------------------------------
/views/ahnentafel/ahnentafel.css:
--------------------------------------------------------------------------------
1 | #ahnentafelAncestorList .highlighted {
2 | background-color: #fad158;
3 | }
4 | #view-container.ahnentafelView {
5 | position: relative;
6 | }
7 | #ahnentafelAncestorList {
8 | background-color: #fff;
9 | padding: 20px;
10 | position: relative;
11 | }
12 | #ahnentafelAncestorList p {
13 | line-height: 1.5em;
14 | margin: 1em 0;
15 | }
16 | #ahnentafelAncestorList h2 {
17 | background-color: #f8cd7d;
18 | padding-left: 0.2em;
19 | font-size: 1.4em;
20 | cursor: pointer;
21 | margin: 0;
22 | }
23 | #ahnentafelAncestorList a.profileLink {
24 | margin: auto;
25 | }
26 |
27 | #ahnentafelAncestorList .ahnentafelPerson,
28 | #ahnentafelAncestorList .ahnentafelPersonShort {
29 | display: flex;
30 | align-items: baseline;
31 | padding: 3px;
32 | }
33 |
34 | #ahnentafelAncestorList .ahnentafelPerson > span.personText,
35 | #ahnentafelAncestorList .ahnentafelPersonShort > span.personText {
36 | margin-left: 0.3em; /* Space between number and name */
37 | }
38 |
39 | #ahnentafelAncestorList .ahnentafelPerson,
40 | #ahnentafelAncestorList .ahnentafelPersonShort {
41 | background-color: #efe;
42 | }
43 |
44 | #ahnentafelAncestorList .ahnentafelPerson.Male,
45 | #ahnentafelAncestorList .ahnentafelPersonShort.Male {
46 | background-color: #eef;
47 | }
48 |
49 | #ahnentafelAncestorList .ahnentafelPerson.Female,
50 | #ahnentafelAncestorList .ahnentafelPersonShort.Female {
51 | background-color: #fee;
52 | }
53 |
54 | #ahnentafelAncestorList div.ahnentafelPerson.highlighted,
55 | #ahnentafelAncestorList div.ahnentafelPerson.highlightedClick,
56 | #ahnentafelAncestorList div.ahnentafelPersonShort.highlighted,
57 | #ahnentafelAncestorList div.ahnentafelPersonShort.highlightedClick {
58 | border: 3px solid gold;
59 | padding: 0;
60 | }
61 | #ahnentafelAncestorList .ahnentafelLink,
62 | #ahnentafelAncestorList .childOf,
63 | #ahnentafelAncestorList .parentOf {
64 | color: #0271fb;
65 | cursor: pointer;
66 | }
67 | #ahnentafelAncestorList .descendantButton {
68 | margin: 0.1em;
69 | padding: 0.1em 0.3em;
70 | position: absolute;
71 | right: -0.1em;
72 | font-size: 1.3em;
73 | }
74 | #ahnentafelAncestorList .descendantButton:hover {
75 | background-color: #f8cd7d;
76 | }
77 | #ahnentafelAncestorList .descendantButton:active {
78 | background-color: orange;
79 | }
80 | #ahnentafelAncestorList .descendantButton.active {
81 | background-color: gold;
82 | }
83 | #ahnentafelAncestorList #masterToggle {
84 | position: absolute;
85 | left: 0.1em;
86 | top: 0.1em;
87 | font-size: 1.5em;
88 | }
89 | #ahnentafelAncestorList .toggleButton {
90 | cursor: pointer;
91 | user-select: none; /* Prevent text selection */
92 | display: inline-block;
93 | transition: transform 0.3s ease; /* Smooth rotation effect */
94 | }
95 | #ahnentafelAncestorList .toggleButton.collapsed {
96 | transform: rotate(-90deg);
97 | }
98 | #ahnentafelAncestorList .range {
99 | font-size: 0.6em;
100 | }
101 |
102 | #ahnentafelAncestorList.tidy .relativeDetails,
103 | #ahnentafelAncestorList.tidy .birthAndDeathDetails {
104 | display: block;
105 | }
106 | #ahnentafelAncestorList.tidy .profileLink {
107 | font-weight: bold;
108 | }
109 |
110 | #ahnentafelAncestorList #generation_1 .ahnentafelPerson .descendantButton {
111 | display: none;
112 | }
113 | #ahnentafelAncestorList h2 span.fillRate,
114 | #ahnentafelAncestorList .duplicateCount {
115 | float: right;
116 | font-size: 0.7em;
117 | margin-right: 0.5em;
118 | }
119 | #ahnentafelAncestorList .duplicateText {
120 | float: right;
121 | font-size: 0.7em;
122 | margin-right: 0.5em;
123 | }
124 |
125 | #ahnentafelAncestorListNavButtons button {
126 | font-size: 0.8em;
127 | padding: 0.2em;
128 | margin: 0.2em;
129 | }
130 | #ahnentafelAncestorListNavButtons {
131 | margin-left: 2em;
132 | }
133 |
134 | #ahnentafelAncestorListNavButtons button:disabled {
135 | background-color: #cccccc;
136 | color: #666666;
137 | border: 1px solid #999999;
138 | }
139 |
140 | /* Additional hover effect for disabled buttons */
141 | #ahnentafelAncestorListNavButtons button:disabled:hover {
142 | background-color: #cccccc;
143 | color: #666666;
144 | }
145 |
146 | #ahnentafelHeaderBox {
147 | display: flex;
148 | align-items: center;
149 | justify-content: space-between;
150 | padding: 0.5em;
151 | margin: auto;
152 | position: sticky;
153 | top: 10px;
154 | }
155 |
156 | #ahnentafelHeaderBox #help-button {
157 | background: darkgreen;
158 | color: #fff;
159 | font-size: 0.8em;
160 | padding: 0.1em 0.5em;
161 | border-radius: 50%;
162 | float: right;
163 | font-weight: bold;
164 | cursor: pointer;
165 | z-index: 11000;
166 | margin-left: 2em;
167 | }
168 | #ahnentafelHeaderBox #help-button:hover {
169 | background: green;
170 | }
171 | #ahnentafelHeaderBox #help-button:active {
172 | background: forestgreen;
173 | }
174 | #ahnentafelHeaderBox button:active {
175 | background: forestgreen;
176 | }
177 |
178 | #ahnentafelHelpText {
179 | display: none;
180 | border: 3px solid forestgreen;
181 | border-radius: 1em;
182 | padding: 0.3em 1em;
183 | margin: 1em auto;
184 | position: absolute;
185 | top: 20px;
186 | left: 100px;
187 | width: 40em;
188 | background: white;
189 | box-shadow: 1em 1em 1em grey;
190 | z-index: 11000;
191 | cursor: default;
192 | }
193 | #ahnentafelHelpText h2 {
194 | margin: 0.2em 0;
195 | text-align: center;
196 | }
197 | #ahnentafelHelpText x {
198 | position: absolute;
199 | top: 0.2em;
200 | right: 0.5em;
201 | font-size: 1.2em;
202 | cursor: pointer;
203 | font-weight: bold;
204 | color: forestgreen;
205 | }
206 | #ahnentafelHelpText x:hover {
207 | color: darkgreen;
208 | }
209 | #ahnentafelHelpText x:active {
210 | color: green;
211 | }
212 | #ahnentafelHelpText ul {
213 | margin: 0.5em;
214 | padding: 0.5em;
215 | }
216 | #ahnentafelHelpText ul ul {
217 | margin: 0 0.5em;
218 | }
219 |
220 | #ahnentafelAncestorList .ahnentafelNumber {
221 | font-weight: bold;
222 | }
223 |
224 | .more-generations-container {
225 | display: flex;
226 | align-items: center;
227 | }
228 |
229 | .more-generations-container input {
230 | width: 3rem;
231 | text-align: center;
232 | }
233 |
234 | .more-generations-container #moreGenerationsButton {
235 | margin-left: 0.5rem;
236 | width: 15em;
237 | }
238 |
239 | #st-status #progressContainer {
240 | width: 100%;
241 | background-color: #ddd;
242 | }
243 |
244 | #wt-status #progressBar {
245 | width: 0%;
246 | height: 30px;
247 | background-color: #4caf50;
248 | text-align: center;
249 | line-height: 30px;
250 | color: white;
251 | }
252 |
253 | #ahnentafelAncestorList .detailsTable {
254 | border-collapse: collapse;
255 | width: auto;
256 | }
257 |
258 | .detailsTable td {
259 | padding-right: 1em;
260 | text-align: left;
261 | }
262 |
263 | #ahnentafelAncestorList .bornCol,
264 | #ahnentafelAncestorList .diedCol {
265 | width: 2em;
266 | }
267 |
268 | #ahnentafelAncestorList .bCol,
269 | #ahnentafelAncestorList .dCol {
270 | /* Narrower widths for 'b.' and 'd.' columns */
271 | width: 1em; /* Adjust based on actual content width */
272 | }
273 |
274 | #ahnentafelAncestorList div.generationContainer {
275 | margin: 0.5em 0;
276 | }
277 |
--------------------------------------------------------------------------------
/views/ancestorLines/ale.css:
--------------------------------------------------------------------------------
1 | #aleContainer {
2 | padding: 0.3em;
3 | }
4 | #theSvg {
5 | overflow-x: auto;
6 | width: 100%;
7 | }
8 | /* Normalise buttons */
9 | .ale button {
10 | font-weight: normal;
11 | font-size: 14px !important;
12 | min-width: auto !important;
13 | padding: 0.5em !important;
14 | margin-left: 0.5em;
15 | margin-right: 0.5em;
16 | }
17 | div.ale table {
18 | margin-bottom: 0;
19 | }
20 | div.ale td {
21 | padding: 0;
22 | }
23 |
24 | #generation,
25 | #maxLevel {
26 | width: 3em;
27 | min-width: 3em;
28 | margin-right: 10px;
29 | margin-top: 0;
30 | }
31 |
32 | #aleOptions {
33 | cursor: pointer;
34 | }
35 | .ale legend {
36 | font-size: 110%;
37 | float: none;
38 | width: auto;
39 | display: table;
40 | margin-bottom: 0;
41 | }
42 |
43 | .ale fieldset {
44 | font-size: 90%;
45 | line-height: 1;
46 | display: block;
47 | margin-inline-start: 2px;
48 | margin-inline-end: 2px;
49 | padding-block-start: 0.35em;
50 | padding-inline-start: 0.75em;
51 | padding-inline-end: 0.75em;
52 | padding-block-end: 0.625em;
53 | min-inline-size: min-content;
54 | border-width: 2px;
55 | border-style: groove;
56 | border-color: rgb(192, 192, 192);
57 | border-image: initial;
58 | border-radius: 5px;
59 | margin-bottom: 10px;
60 | }
61 | .ale fieldset input {
62 | margin: 0;
63 | }
64 | .ale fieldset label {
65 | margin-right: 0;
66 | }
67 | .ale fieldset label.left {
68 | margin-left: 10px;
69 | }
70 | .ale fieldset label.right {
71 | margin-right: 10px;
72 | }
73 |
74 | .ale .node circle {
75 | fill: #fff;
76 | stroke: steelblue;
77 | stroke-width: 3px;
78 | }
79 |
80 | .ale .node circle.ofinterest {
81 | stroke: coral;
82 | }
83 |
84 | .ale .node circle.end {
85 | stroke: red;
86 | }
87 |
88 | .ale .node rect.dup.marked {
89 | stroke-width: 3px;
90 | stroke: black;
91 | }
92 |
93 | .ale .treeHeader,
94 | .ale .node text {
95 | font: 12px sans-serif;
96 | }
97 |
98 | .ale text.marked {
99 | font-size: 14px;
100 | font-weight: bold;
101 | }
102 |
103 | .ale .link {
104 | fill: none;
105 | stroke: #ccc;
106 | stroke-width: 2px;
107 | }
108 |
109 | .ale .link.ofinterest {
110 | stroke: lightcoral;
111 | }
112 |
113 | .ale .link.marked {
114 | stroke: green;
115 | }
116 |
117 | .ale .links line {
118 | stroke: #999;
119 | stroke-opacity: 0.6;
120 | }
121 |
122 | .ale .nodes circle {
123 | stroke: #fff;
124 | stroke-width: 1.5px;
125 | }
126 |
127 | #aleBrickWallColour {
128 | width: 2em;
129 | padding: 2px;
130 | }
131 |
132 | .ale #edgeFactor,
133 | .ale #tHFactor {
134 | width: 4em;
135 | }
136 |
137 | .ale #help-text {
138 | display: none;
139 | border: 3px solid forestgreen;
140 | border-radius: 1em;
141 | padding: 0.3em 1em;
142 | margin: 1em auto;
143 | position: absolute;
144 | top: 20px;
145 | right: 100px;
146 | width: 50em;
147 | background: white;
148 | box-shadow: 1em 1em 1em grey;
149 | z-index: 11000;
150 | cursor: default;
151 | }
152 |
153 | .ale xx {
154 | position: absolute;
155 | top: 0.2em;
156 | right: 0.6em;
157 | font-size: 1em;
158 | cursor: pointer;
159 | font-weight: bold;
160 | color: red;
161 | }
162 |
163 | .ale #help-text:hover {
164 | cursor: grab;
165 | }
166 | .ale #help-text:active {
167 | cursor: grabbing;
168 | }
169 | .ale #help-text h3 {
170 | font-size: 1.3em;
171 | margin-bottom: 0.5em;
172 | }
173 |
174 | .ale .bioReport {
175 | background: white;
176 | border-radius: 1em;
177 | border: 3px solid forestgreen;
178 | box-shadow: 0.5em 0.5em 0.5em 0.5em #ccc;
179 | cursor: grab;
180 | display: none;
181 | position: absolute;
182 | padding: 1.5em 1em 1em;
183 | /* white-space: nowrap; */
184 | z-index: 10000;
185 | }
186 | .ale .bioReport caption {
187 | font-family: sans-serif;
188 | font-size: 1.2em;
189 | font-weight: bold;
190 | text-align: left;
191 | padding: 0.5em;
192 | background: white;
193 | border-top: 1px solid forestgreen;
194 | border-bottom: 1px solid forestgreen;
195 | }
196 |
197 | .ale .bioReport w {
198 | position: absolute;
199 | top: 0;
200 | left: 0.5em;
201 | font-weight: bold;
202 | cursor: pointer;
203 | }
204 | .ale .bioReport.wrap {
205 | width: 80%;
206 | white-space: normal;
207 | }
208 | .ale .bioReportTable ol {
209 | margin-left: 2em;
210 | }
211 | .ale .bioReportTable td {
212 | border-top: none;
213 | border-bottom: none;
214 | }
215 |
216 | @media print {
217 | .ale-not-printable {
218 | display: none;
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/views/ancestorLines/ale_view.js:
--------------------------------------------------------------------------------
1 | import { AncestorLinesExplorer } from "./ancestor_lines_explorer.js";
2 |
3 | window.ALEView = class ALEView extends View {
4 | static #DESCRIPTION = "Click on the question mark in the green box below right for help.";
5 | meta() {
6 | return {
7 | title: "Ancestor Lines Explorer",
8 | description: ALEView.#DESCRIPTION,
9 | docs: "",
10 | params: [
11 | "maxgen",
12 | "limitgen",
13 | "poi",
14 | "exploi",
15 | "conn",
16 | "efactor",
17 | "hfactor",
18 | "onlyloi",
19 | "lonly",
20 | "bwcolour",
21 | "posrel",
22 | "priv",
23 | "anon",
24 | "bwnop",
25 | "bw1p",
26 | "bwbio",
27 | "bwnosp",
28 | "bwnoch",
29 | ],
30 | };
31 | }
32 |
33 | init(container_selector, person_id, params) {
34 | wtViewRegistry.setInfoPanel(ALEView.#DESCRIPTION);
35 | wtViewRegistry.showInfoPanel();
36 | const ale = new AncestorLinesExplorer(container_selector, person_id, params);
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/views/ancestorLines/api.js:
--------------------------------------------------------------------------------
1 | export class API {
2 | static APP_ID = "AncestorLineExplorer";
3 | static MAX_API_DEPTH = 10; // how many generations we are prepared to retrieve per API call
4 | static GET_PERSON_LIMIT = 1000;
5 | static PRIMARY_FIELDS = [
6 | "BirthDate",
7 | "BirthDateDecade",
8 | "BirthLocation",
9 | "DataStatus",
10 | "DeathDate",
11 | "DeathDateDecade",
12 | "DeathLocation",
13 | "Derived.BirthName",
14 | "Derived.BirthNamePrivate",
15 | "Father",
16 | "FirstName",
17 | "Gender",
18 | "HasChildren",
19 | "Id",
20 | "IsLiving",
21 | "LastNameAtBirth",
22 | "LastNameCurrent",
23 | "LastNameOther",
24 | "MiddleName",
25 | "Mother",
26 | "Name",
27 | "Nicknames",
28 | "NoChildren",
29 | // "Photo",
30 | "Prefix",
31 | "Privacy",
32 | "RealName",
33 | "Suffix",
34 |
35 | "Spouse", // added for Person Popups
36 | "PhotoData"
37 | ];
38 |
39 | static FOR_BIO_CHECK = ["Bio", "IsMember", "Manager"];
40 |
41 | static async getPeople(ids, ancestors = 0, start = 0, limit = API.GET_PERSON_LIMIT, withBios = false) {
42 | const fields = withBios ? API.PRIMARY_FIELDS.concat(API.FOR_BIO_CHECK) : API.PRIMARY_FIELDS;
43 | const [status, resultByKey, people] = await WikiTreeAPI.getPeople(API.APP_ID, ids, fields, {
44 | ancestors: ancestors,
45 | start: start,
46 | limit: limit,
47 | });
48 | if (status != "") {
49 | console.warn(`getpeople returned status: ${status}`);
50 | }
51 | return [status, resultByKey, people];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/views/ancestorLines/jquery.floatingscroll.css:
--------------------------------------------------------------------------------
1 | .fl-scrolls{overflow:auto;position:fixed}.fl-scrolls div{overflow:hidden;pointer-events:none}.fl-scrolls div:before{content:"\A0"}.fl-scrolls,.fl-scrolls div{font-size:1px;line-height:0;margin:0;padding:0}.fl-scrolls-hidden div:before{content:"\A0\A0"}.fl-scrolls-viewport{position:relative}.fl-scrolls-body{overflow:auto}.fl-scrolls-viewport .fl-scrolls{position:absolute}.fl-scrolls-hoverable .fl-scrolls{opacity:0;transition:opacity .5s .3s}.fl-scrolls-hoverable:hover .fl-scrolls{opacity:1}.fl-scrolls:not([data-orientation]),.fl-scrolls[data-orientation=horizontal]{bottom:0;min-height:17px}.fl-scrolls:not([data-orientation]) div,.fl-scrolls[data-orientation=horizontal] div{height:1px}.fl-scrolls-hidden.fl-scrolls:not([data-orientation]),.fl-scrolls-hidden.fl-scrolls[data-orientation=horizontal]{bottom:9999px}.fl-scrolls-viewport .fl-scrolls:not([data-orientation]),.fl-scrolls-viewport .fl-scrolls[data-orientation=horizontal]{left:0}.fl-scrolls[data-orientation=vertical]{right:0;min-width:17px}.fl-scrolls[data-orientation=vertical] div{width:1px}.fl-scrolls-hidden.fl-scrolls[data-orientation=vertical]{right:9999px}.fl-scrolls-viewport .fl-scrolls[data-orientation=vertical]{top:0}
--------------------------------------------------------------------------------
/views/ancestorLines/jquery.floatingscroll.es6.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | floating-scroll v3.2.0
3 | https://amphiluke.github.io/floating-scroll/
4 | (c) 2023 Amphiluke
5 | */
6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jQuery)}(this,(function(e){"use strict";const t="horizontal",i="vertical";let n={init(e,i){let n=this;n.orientationProps=(e=>{let i=e===t;return{ORIENTATION:e,SIZE:i?"width":"height",X_SIZE:i?"height":"width",OFFSET_SIZE:i?"offsetWidth":"offsetHeight",OFFSET_X_SIZE:i?"offsetHeight":"offsetWidth",CLIENT_SIZE:i?"clientWidth":"clientHeight",CLIENT_X_SIZE:i?"clientHeight":"clientWidth",INNER_X_SIZE:i?"innerHeight":"innerWidth",SCROLL_SIZE:i?"scrollWidth":"scrollHeight",SCROLL_POS:i?"scrollLeft":"scrollTop",START:i?"left":"top",X_START:i?"top":"left",X_END:i?"bottom":"right"}})(i);let o=e.closest(".fl-scrolls-body");o.length&&(n.scrollBody=o),n.container=e[0],n.visible=!0,n.initWidget(),n.updateAPI(),n.addEventHandlers(),n.skipSyncContainer=n.skipSyncWidget=!1},initWidget(){let t=this;const{ORIENTATION:i,SIZE:n,SCROLL_SIZE:o}=t.orientationProps;let l=t.widget=e(``);e("").appendTo(l)[n](t.container[o]),l.appendTo(t.container)},addEventHandlers(){let t=this;(t.eventHandlers=[{$el:e(window),handlers:{"destroyDetached.fscroll"({namespace:e}){"fscroll"===e&&t.destroyDetachedAPI()}}},{$el:t.scrollBody||e(window),handlers:{scroll(){t.updateAPI()},resize(){t.updateAPI()}}},{$el:t.widget,handlers:{scroll(){t.visible&&!t.skipSyncContainer&&t.syncContainer(),t.skipSyncContainer=!1}}},{$el:e(t.container),handlers:{scroll(){t.skipSyncWidget||t.syncWidget(),t.skipSyncWidget=!1},focusin(){setTimeout((()=>{t.widget&&t.syncWidget()}),0)},"update.fscroll"({namespace:e}){"fscroll"===e&&t.updateAPI()},"destroy.fscroll"({namespace:e}){"fscroll"===e&&t.destroyAPI()}}}]).forEach((({$el:e,handlers:t})=>e.bind(t)))},checkVisibility(){let e=this,{widget:t,container:i,scrollBody:n}=e;const{SCROLL_SIZE:o,OFFSET_SIZE:l,X_START:s,X_END:r,INNER_X_SIZE:d,CLIENT_X_SIZE:c}=e.orientationProps;let a=t[0][o]<=t[0][l];if(!a){let e=i.getBoundingClientRect(),t=n?n[0].getBoundingClientRect()[r]:window[d]||document.documentElement[c];a=e[r]<=t||e[s]>t}e.visible===a&&(e.visible=!a,t.toggleClass("fl-scrolls-hidden"))},syncContainer(){let e=this;const{SCROLL_POS:t}=e.orientationProps;let i=e.widget[0][t];e.container[t]!==i&&(e.skipSyncWidget=!0,e.container[t]=i)},syncWidget(){let e=this;const{SCROLL_POS:t}=e.orientationProps;let i=e.container[t];e.widget[0][t]!==i&&(e.skipSyncContainer=!0,e.widget[0][t]=i)},updateAPI(){let t=this;const{SIZE:i,X_SIZE:n,OFFSET_X_SIZE:o,CLIENT_SIZE:l,CLIENT_X_SIZE:s,SCROLL_SIZE:r,START:d}=t.orientationProps;let{widget:c,container:a,scrollBody:h}=t,S=a[l],f=a[r];c[i](S),h||c.css(d,`${a.getBoundingClientRect()[d]}px`),e("div",c)[i](f),f>S&&c[n](c[0][o]-c[0][s]+1),t.syncWidget(),t.checkVisibility()},destroyAPI(){let e=this;e.eventHandlers.forEach((({$el:e,handlers:t})=>e.unbind(t))),e.widget.remove(),e.eventHandlers=e.widget=e.container=e.scrollBody=null},destroyDetachedAPI(){e.contains(document.body,this.container)||this.destroyAPI()}};e.fn.floatingScroll=function(o="init",l={}){if("init"===o){let{orientation:o=t}=l;if(o!==t&&o!==i)throw new Error(`Scrollbar orientation should be either “${t}” or “${i}”`);this.each(((t,i)=>Object.create(n).init(e(i),o)))}else Object.prototype.hasOwnProperty.call(n,`${o}API`)&&this.trigger(`${o}.fscroll`);return this},e((()=>{e("body [data-fl-scrolls]").each(((t,i)=>{let n=e(i);n.floatingScroll("init",n.data("flScrolls")||{})}))}))}));
7 |
--------------------------------------------------------------------------------
/views/ancestorsCemeteries/ancestorsCemeteries.js:
--------------------------------------------------------------------------------
1 | // Application the get the cemeteries where Ancestors are buried and place in a list.
2 | // Created By: Kohn-970
3 |
4 | /*
5 | TODO:
6 | 1. Add function to allow people to select depth of ancestors.
7 | 2. Have app check other areas for information about cemeteries.
8 | */
9 |
10 | window.AncestorsCemeteriesView = class AncestorsCemeteriesView extends View {
11 | static APP_ID = "Cemeteries";
12 |
13 | meta() {
14 | return {
15 | title: "Ancestors Cemeteries",
16 | description: "App to return where ancestors are buried.",
17 | params: ["ancestors"],
18 | };
19 | }
20 |
21 | async init(container_selector, person_id) {
22 | const personData = await WikiTreeAPI.getPerson("ancestorsCemeteries", person_id, ["FirstName"]);
23 | const name = personData["_data"]["FirstName"];
24 |
25 | getAncestors();
26 |
27 | // Retrieve the ancestors from the api
28 | function getAncestors() {
29 | WikiTreeAPI.postToAPI({
30 | appId: AncestorsCemeteriesView.APP_ID,
31 | action: "getAncestors",
32 | key: person_id,
33 | depth: 5,
34 | fields: 'Name,LastNameAtBirth,FirstName,Categories',
35 | resolveRedirect: 1,
36 | }).then(function (data) {
37 | const tblResult = generateTable(data[0].ancestors);
38 | document.getElementById('view-container').appendChild(tblResult);
39 |
40 | new DataTable('#cemeteriesTable');
41 | })
42 | }
43 | }
44 | };
45 |
46 | function findCemetery(data) {
47 | let searchWords = ['burial', 'cemetery', 'memorial', 'gardens', 'park', 'potters', 'field']
48 | const regex = new RegExp(searchWords.join("|"), "i"); // 'i' for case-insensitive
49 | const matchedWords = data.filter(word => regex.test(word));
50 | let result = JSON.stringify(matchedWords);
51 | result = result.replace(/[_]/g, ' ').replace(/[\[\]\'\"]/g, '');
52 |
53 | return result;
54 | }
55 |
56 |
57 | function generateTable(data) {
58 | // console.log(data);
59 | // creates a element and a element
60 | let tbl = document.createElement("table");
61 | tbl.id = 'cemeteriesTable';
62 | let caption = tbl.createCaption();
63 | caption.textContent = "Ancestors Cemeteries";
64 |
65 | const tblBody = document.createElement("tbody");
66 |
67 | // create table header
68 | const thead = document.createElement("thead");
69 | const headerRow = document.createElement('tr');
70 | const headers = Object.keys(data[0]);
71 |
72 | headers.forEach(header => {
73 | const th = document.createElement('th');
74 |
75 | if (header == 'Name') {
76 | header = 'WikiTree ID';
77 | }
78 | else if (header == 'LastNameAtBirth') {
79 | header = 'Last Name';
80 | }
81 | else if (header == 'FirstName') {
82 | header = 'First Name';
83 | }
84 | else if (header == 'Categories') {
85 | header = 'Cemetery';
86 | }
87 |
88 |
89 | th.textContent = header.charAt(0).toUpperCase() + header.slice(1); // Capitalize the first letter
90 | headerRow.appendChild(th);
91 | });
92 |
93 | thead.appendChild(headerRow);
94 | tbl.appendChild(thead);
95 |
96 | data.forEach(item => {
97 | const row = document.createElement('tr');
98 |
99 | headers.forEach(header => {
100 | const td = document.createElement('td');
101 | if (header == "Categories") {
102 | const cemetery = findCemetery(item[header]);
103 | // console.log(cemetery);
104 | // console.log(cemetery.length)
105 | if (cemetery.length > 1) {
106 | td.textContent = cemetery;
107 | }
108 | else {
109 | td.textContent = "NA";
110 | }
111 | } else if (header == "Name") {
112 | const person = item[header];
113 | const url = "https://www.wikitree.com/wiki/" + person;
114 | td.innerHTML = `${person}`;
115 | }
116 | else {
117 | td.textContent = item[header];
118 | }
119 | row.appendChild(td);
120 | });
121 | tblBody.appendChild(row);
122 | });
123 |
124 | // put the in the
125 | tbl.appendChild(tblBody);
126 | // appends into
127 | document.body.appendChild(tbl);
128 | // sets the border attribute of tbl to '2'
129 | tbl.setAttribute("border", "2");
130 |
131 | return tbl;
132 | }
133 |
134 |
135 |
--------------------------------------------------------------------------------
/views/cc7/cc7View.js:
--------------------------------------------------------------------------------
1 | import { CC7, CC7UrlParams } from "./js/cc7.js";
2 |
3 | window.CC7View = class CC7View extends View {
4 | constructor() {
5 | super();
6 | this.overflow = undefined;
7 | }
8 |
9 | meta() {
10 | return {
11 | title: "CC7 Views",
12 | description: CC7.LONG_LOAD_WARNING,
13 | docs: "",
14 | params: CC7UrlParams,
15 | };
16 | }
17 |
18 | init(container_selector, person_id, params) {
19 | // Our view fiddles with the overflow style value of view-container, so we want to reset it to its original
20 | // value once the user is done with our view.
21 | // However, init() can be called multiple times while the view is active (i.e. everytime the GO button is clicked)
22 | // so we save the overflow value only if close() had been called since we last saved it
23 | if (!this.overflow) {
24 | // Note this can't be done with the JQuery css() function as that returns evaluated style.
25 | // We want only the value from the "style" attribute (which should be null)
26 | this.overflow = document.querySelector("#view-container").style.overflow || ""; // not $("#view-container").css("overflow");
27 | }
28 | const cc7 = new CC7(container_selector, person_id, params);
29 | }
30 |
31 | close() {
32 | // Another view is about to be activated, restore the original overflow value of view-container
33 | $("#view-container").css({
34 | overflow: this.overflow,
35 | });
36 | this.overflow = undefined;
37 | }
38 | };
39 |
40 | CC7View.cancelSettings = function () {
41 | let theDIV = document.getElementById("settingsDIV");
42 | theDIV.style.display = "none";
43 | };
44 |
--------------------------------------------------------------------------------
/views/cc7/images/50px-Remember_the_Children-26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/50px-Remember_the_Children-26.png
--------------------------------------------------------------------------------
/views/cc7/images/Home_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/Home_icon.png
--------------------------------------------------------------------------------
/views/cc7/images/RTC_-_Pictures-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/RTC_-_Pictures-6.png
--------------------------------------------------------------------------------
/views/cc7/images/Remember_the_Children-21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/Remember_the_Children-21.png
--------------------------------------------------------------------------------
/views/cc7/images/Remember_the_Children-27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/Remember_the_Children-27.png
--------------------------------------------------------------------------------
/views/cc7/images/aids-ribbon-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/aids-ribbon-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/baby_bricks_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/baby_bricks_small.png
--------------------------------------------------------------------------------
/views/cc7/images/blue_bricks_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/blue_bricks_small.jpg
--------------------------------------------------------------------------------
/views/cc7/images/butterfly-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/butterfly-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/candle-light-color-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/candle-light-color-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/candle-light-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/candle-light-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/checkbox-cross-red-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/checkbox-cross-red-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/checkmark-box-green-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/checkmark-box-green-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/diedYoung.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/diedYoung.png
--------------------------------------------------------------------------------
/views/cc7/images/flower-plant-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/flower-plant-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/flower-rose-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/flower-rose-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/load-33_128.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/load-33_128.gif
--------------------------------------------------------------------------------
/views/cc7/images/pink-and-blue-ribbon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/pink-and-blue-ribbon.png
--------------------------------------------------------------------------------
/views/cc7/images/pink_bricks_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/pink_bricks_small.jpg
--------------------------------------------------------------------------------
/views/cc7/images/privacy_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_open.png
--------------------------------------------------------------------------------
/views/cc7/images/privacy_privacy35.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_privacy35.png
--------------------------------------------------------------------------------
/views/cc7/images/privacy_private.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_private.png
--------------------------------------------------------------------------------
/views/cc7/images/privacy_public-bio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_public-bio.png
--------------------------------------------------------------------------------
/views/cc7/images/privacy_public-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_public-tree.png
--------------------------------------------------------------------------------
/views/cc7/images/privacy_public.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_public.png
--------------------------------------------------------------------------------
/views/cc7/images/privacy_unlisted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/privacy_unlisted.png
--------------------------------------------------------------------------------
/views/cc7/images/purple_bricks_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/purple_bricks_small.jpg
--------------------------------------------------------------------------------
/views/cc7/images/question-mark-circle-outline-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/question-mark-circle-outline-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/reverse.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/views/cc7/images/setting-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/setting-icon.png
--------------------------------------------------------------------------------
/views/cc7/images/spouse_bricks_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/spouse_bricks_small.png
--------------------------------------------------------------------------------
/views/cc7/images/timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/timeline.png
--------------------------------------------------------------------------------
/views/cc7/images/tree.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/cc7/images/tree.gif
--------------------------------------------------------------------------------
/views/cc7/js/FileSaver.min.js:
--------------------------------------------------------------------------------
1 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
2 |
3 | //# sourceMappingURL=FileSaver.min.js.map
--------------------------------------------------------------------------------
/views/cc7/js/LanceView.js:
--------------------------------------------------------------------------------
1 | import { CC7Utils } from "./CC7Utils.js";
2 |
3 | export class LanceView {
4 | static async build() {
5 | $("#peopleTable").hide();
6 | const surnames = {
7 | degree_1: [],
8 | degree_2: [],
9 | degree_3: [],
10 | degree_4: [],
11 | degree_5: [],
12 | degree_6: [],
13 | degree_7: [],
14 | };
15 | const subset = $("#cc7Subset").val();
16 | const lanceTable = $(
17 | `` +
18 | "" +
19 | "
" +
20 | "" +
21 | "" +
22 | "
" +
23 | "" +
24 | "
"
25 | );
26 | if ($("#lanceTable").length) {
27 | $("#lanceTable").eq(0).replaceWith(lanceTable);
28 | } else {
29 | $("#peopleTable").before(lanceTable);
30 | }
31 | for (let i = 1; i < 8; i++) {
32 | let aHeading = $("" + i + ".
| ");
33 | lanceTable.find("thead tr").append(aHeading);
34 | let aCell = $("
| ");
35 | lanceTable.find("tbody tr").append(aCell);
36 | }
37 |
38 | for (let aPerson of window.people.values()) {
39 | // Ignore profiles not in the selected subset
40 | if (!CC7Utils.profileIsInSubset(aPerson, subset)) continue;
41 | if (!aPerson.Missing) {
42 | CC7Utils.addMissingBits(aPerson);
43 | }
44 | const theDegree = aPerson.Meta.Degrees;
45 | const aName = new PersonName(aPerson);
46 | const theName = CC7Utils.formDisplayName(aPerson, aName);
47 | const theParts = aName.getParts(["LastNameAtBirth", "FirstName"]);
48 | const theLNAB = theParts.get("LastNameAtBirth");
49 | const theFirstName = theParts.get("FirstName");
50 |
51 | if (CC7Utils.isOK(theDegree) && theDegree <= window.cc7Degree) {
52 | if (!surnames["degree_" + theDegree].includes(theLNAB)) {
53 | // Add the LNAB to the appropriate degree column header
54 | surnames["degree_" + theDegree].push(theLNAB);
55 | const anLi2 = $(
56 | "" +
63 | theLNAB +
64 | ""
65 | );
66 | $("#degreeHeading_" + theDegree)
67 | .find("ol")
68 | .append(anLi2);
69 | anLi2
70 | .find("a")
71 | .off("click")
72 | .on("click", function (e) {
73 | e.preventDefault();
74 |
75 | const degreeNum = $(this).closest("th").prop("id").slice(-1);
76 | const theList = $("#degree_" + degreeNum).find("li");
77 | if ($(this).attr("data-clicked") == 1) {
78 | theList.show();
79 | $("#lanceTable thead a").each(function () {
80 | $(this).attr("data-clicked", 0);
81 | });
82 | } else {
83 | const thisLNAB = $(this).closest("li").data("lnab");
84 | $("#lanceTable thead a").each(function () {
85 | $(this).attr("data-clicked", 0);
86 | });
87 | $(this).attr("data-clicked", 1);
88 | theList.each(function () {
89 | if ($(this).data("lnab") != thisLNAB) {
90 | $(this).hide();
91 | } else {
92 | $(this).show();
93 | }
94 | });
95 | }
96 | });
97 | }
98 | // Add the person to the appropriate column
99 | const linkName = CC7Utils.htmlEntities(aPerson.Name);
100 | const missing = CC7Utils.missingThings(aPerson);
101 | const anLi = $(
102 | "' +
111 | CC7Utils.profileLink(linkName, theName) +
112 | " " +
113 | missing.missingIcons +
114 | ""
115 | );
116 | $("#degree_" + theDegree)
117 | .find("ol")
118 | .append(anLi);
119 | }
120 | }
121 |
122 | $("td ol,th ol").each(function () {
123 | LanceView.sortList($(this), "lnab");
124 | });
125 | $("#peopleTable").removeClass("active");
126 | $("#lanceTable").addClass("active");
127 | }
128 |
129 | static sortList(aList, dataField, reverse = 0) {
130 | const rows = aList.find("li");
131 | rows.sort(function (a, b) {
132 | if (reverse == 0) {
133 | return $(a).data(dataField).localeCompare($(b).data(dataField));
134 | } else {
135 | return $(b).data(dataField).localeCompare($(a).data(dataField));
136 | }
137 | });
138 | rows.appendTo(aList);
139 | LanceView.secondarySort3(aList, "lnab", "first-name", 1);
140 | }
141 |
142 | static secondarySort3(aList, dataThing1, dataThing2, isText = 0, reverse = 0) {
143 | let lastOne = "Me";
144 | let tempArr = [lastOne];
145 |
146 | const rows = aList.find("li");
147 | rows.each(function (index) {
148 | if ($(this).data(dataThing1) == lastOne) {
149 | tempArr.push($(this));
150 | } else {
151 | tempArr.sort(function (a, b) {
152 | if (isText == 1) {
153 | if (reverse == 0) {
154 | return $(a).data(dataThing2).localeCompare($(b).data(dataThing2));
155 | } else {
156 | return $(b).data(dataThing2).localeCompare($(a).data(dataThing2));
157 | }
158 | } else {
159 | if (reverse == 0) {
160 | return $(a).data(dataThing2) - $(b).data(dataThing2);
161 | } else {
162 | return $(b).data(dataThing2) - $(a).data(dataThing2);
163 | }
164 | }
165 | });
166 |
167 | tempArr.forEach(function (item) {
168 | if (lastOne != "Me") {
169 | item.insertBefore(rows.eq(index));
170 | }
171 | });
172 | tempArr = [$(this)];
173 | }
174 | lastOne = $(this).data(dataThing1);
175 | });
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/views/cc7/js/MissingLinksView.js:
--------------------------------------------------------------------------------
1 | import { Settings } from "./Settings.js";
2 | import { PRIVACY_LEVELS } from "./PeopleTable.js";
3 | import { CC7Utils } from "./CC7Utils.js";
4 | import { CC7Notes } from "./CC7Notes.js";
5 |
6 | export class MissingLinksView {
7 | static async buildView() {
8 | const missingLinksTable = $(`
9 |
10 |
11 |
12 | Privacy |
13 | Degree |
14 | First |
15 | Last |
16 | Birth Date |
17 | Birth Place |
18 | Father? |
19 | Mother? |
20 | # Spouses |
21 | # Children |
22 | Check for Dupes |
23 | WikiTree ID |
24 |
25 |
26 |
27 |
28 |
`);
29 | missingLinksTable.insertBefore($("#peopleTable"));
30 |
31 | let idsAndStatus = await CC7Notes.getIdsAndStatus();
32 | const idsWithNotes = new Map(idsAndStatus);
33 | idsAndStatus = null;
34 |
35 | // sort the people by degree
36 | // TODO also sort by birthdate
37 | const mapArray = Array.from(window.people);
38 | mapArray.sort((a, b) => a[1]["Meta"]["Degrees"] - b[1]["Meta"]["Degrees"]);
39 | const sortedMap = new Map(mapArray);
40 |
41 | for (let person of sortedMap.values()) {
42 | const privacy = person.Privacy || "?";
43 | const degree = person.Meta.Degrees;
44 | const first = person.RealName;
45 | const last = person.LastNameAtBirth;
46 | const birthDate = person.BirthDate;
47 | const birthPlace = person.BirthLocation;
48 | const children = person.Child.length;
49 | const spouses = person.Spouses.length;
50 | const wikiTreeId = person.Name;
51 |
52 | if (CC7Utils.isMissingFamily(person)) {
53 | // create row
54 | const hasNote = idsWithNotes.has(person.Id);
55 | let status = hasNote ? idsWithNotes.get(person.Id) : "";
56 | if (status != "") status = " " + status;
57 | const degreeCell = `${degree}° | `;
60 |
61 | const newRow = $(`
62 |
63 | .img}) |
66 | ${degreeCell}
67 | ${first} |
68 | ${last} |
69 | ${birthDate ? birthDate : ""} |
70 | ${birthPlace ? birthPlace : ""} |
71 | ${person.Father > 0 ? "yes" : "no"} |
72 | ${person.Mother > 0 ? "yes" : "no"} |
73 | 0 && person.DataStatus?.Spouse != "blank"
77 | ? "possible-lead"
78 | : ""
79 | }">${spouses} |
80 | 0 && person.NoChildren != 1
84 | ? "possible-lead"
85 | : ""
86 | }">${children} |
87 | Check |
90 | ${wikiTreeId} |
91 |
92 | `);
93 |
94 | // add row to table
95 | $("#missingLinksTable tbody").append(newRow);
96 | }
97 | }
98 | }
99 |
100 | static sortPeople() {}
101 | }
102 |
--------------------------------------------------------------------------------
/views/cc7/js/date.format.js:
--------------------------------------------------------------------------------
1 | /* eslint no-extend-native: 0 */
2 |
3 | (function () {
4 | // Defining locale
5 | Date.shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
6 | Date.longMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
7 | Date.shortDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
8 | Date.longDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
9 | // Defining patterns
10 | var replaceChars = {
11 | // Day
12 | d: function () { var d = this.getDate(); return (d < 10 ? '0' : '') + d },
13 | D: function () { return Date.shortDays[this.getDay()] },
14 | j: function () { return this.getDate() },
15 | l: function () { return Date.longDays[this.getDay()] },
16 | N: function () { var N = this.getDay(); return (N === 0 ? 7 : N) },
17 | S: function () { var S = this.getDate(); return (S % 10 === 1 && S !== 11 ? 'st' : (S % 10 === 2 && S !== 12 ? 'nd' : (S % 10 === 3 && S !== 13 ? 'rd' : 'th'))) },
18 | w: function () { return this.getDay() },
19 | z: function () { var d = new Date(this.getFullYear(), 0, 1); return Math.ceil((this - d) / 86400000) },
20 | // Week
21 | W: function () {
22 | var target = new Date(this.valueOf())
23 | var dayNr = (this.getDay() + 6) % 7
24 | target.setDate(target.getDate() - dayNr + 3)
25 | var firstThursday = target.valueOf()
26 | target.setMonth(0, 1)
27 | if (target.getDay() !== 4) {
28 | target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7)
29 | }
30 | var retVal = 1 + Math.ceil((firstThursday - target) / 604800000)
31 |
32 | return (retVal < 10 ? '0' + retVal : retVal)
33 | },
34 | // Month
35 | F: function () { return Date.longMonths[this.getMonth()] },
36 | m: function () { var m = this.getMonth(); return (m < 9 ? '0' : '') + (m + 1) },
37 | M: function () { return Date.shortMonths[this.getMonth()] },
38 | n: function () { return this.getMonth() + 1 },
39 | t: function () {
40 | var year = this.getFullYear()
41 | var nextMonth = this.getMonth() + 1
42 | if (nextMonth === 12) {
43 | year = year++
44 | nextMonth = 0
45 | }
46 | return new Date(year, nextMonth, 0).getDate()
47 | },
48 | // Year
49 | L: function () { var L = this.getFullYear(); return (L % 400 === 0 || (L % 100 !== 0 && L % 4 === 0)) },
50 | o: function () { var d = new Date(this.valueOf()); d.setDate(d.getDate() - ((this.getDay() + 6) % 7) + 3); return d.getFullYear() },
51 | Y: function () { return this.getFullYear() },
52 | y: function () { return ('' + this.getFullYear()).substr(2) },
53 | // Time
54 | a: function () { return this.getHours() < 12 ? 'am' : 'pm' },
55 | A: function () { return this.getHours() < 12 ? 'AM' : 'PM' },
56 | B: function () { return Math.floor((((this.getUTCHours() + 1) % 24) + this.getUTCMinutes() / 60 + this.getUTCSeconds() / 3600) * 1000 / 24) },
57 | g: function () { return this.getHours() % 12 || 12 },
58 | G: function () { return this.getHours() },
59 | h: function () { var h = this.getHours(); return ((h % 12 || 12) < 10 ? '0' : '') + (h % 12 || 12) },
60 | H: function () { var H = this.getHours(); return (H < 10 ? '0' : '') + H },
61 | i: function () { var i = this.getMinutes(); return (i < 10 ? '0' : '') + i },
62 | s: function () { var s = this.getSeconds(); return (s < 10 ? '0' : '') + s },
63 | v: function () { var v = this.getMilliseconds(); return (v < 10 ? '00' : (v < 100 ? '0' : '')) + v },
64 | // Timezone
65 | e: function () { return Intl.DateTimeFormat().resolvedOptions().timeZone },
66 | I: function () {
67 | var DST = null
68 | for (var i = 0; i < 12; ++i) {
69 | var d = new Date(this.getFullYear(), i, 1)
70 | var offset = d.getTimezoneOffset()
71 |
72 | if (DST === null) DST = offset
73 | else if (offset < DST) { DST = offset; break } else if (offset > DST) break
74 | }
75 | return (this.getTimezoneOffset() === DST) | 0
76 | },
77 | O: function () { var O = this.getTimezoneOffset(); return (-O < 0 ? '-' : '+') + (Math.abs(O / 60) < 10 ? '0' : '') + Math.floor(Math.abs(O / 60)) + (Math.abs(O % 60) === 0 ? '00' : ((Math.abs(O % 60) < 10 ? '0' : '')) + (Math.abs(O % 60))) },
78 | P: function () { var P = this.getTimezoneOffset(); return (-P < 0 ? '-' : '+') + (Math.abs(P / 60) < 10 ? '0' : '') + Math.floor(Math.abs(P / 60)) + ':' + (Math.abs(P % 60) === 0 ? '00' : ((Math.abs(P % 60) < 10 ? '0' : '')) + (Math.abs(P % 60))) },
79 | T: function () { var tz = this.toLocaleTimeString(navigator.language, {timeZoneName: 'short'}).split(' '); return tz[tz.length - 1] },
80 | Z: function () { return -this.getTimezoneOffset() * 60 },
81 | // Full Date/Time
82 | c: function () { return this.format('Y-m-d\\TH:i:sP') },
83 | r: function () { return this.toString() },
84 | U: function () { return Math.floor(this.getTime() / 1000) }
85 | }
86 |
87 | // Simulates PHP's date function
88 | Date.prototype.format = function (format) {
89 | var date = this
90 | return format.replace(/(\\?)(.)/g, function (_, esc, chr) {
91 | return (esc === '' && replaceChars[chr]) ? replaceChars[chr].call(date) : chr
92 | })
93 | }
94 | }).call(this)
95 |
--------------------------------------------------------------------------------
/views/compactCouplesTree/cct.css:
--------------------------------------------------------------------------------
1 | /* Normalise buttons */
2 | .cct button {
3 | font-weight: normal;
4 | font-size: 14px !important;
5 | min-width: auto !important;
6 | padding: 0.5em !important;
7 | margin-left: 0.5em;
8 | margin-right: 0.5em;
9 | }
10 |
11 | .cct {
12 | --a-colour: rgba(102, 102, 204, 0.6);
13 | --b-colour: rgba(204, 102, 102, 0.6);
14 | --o-colour: rgba(102, 204, 102, 0.6);
15 | --a-full: rgba(102, 102, 204, 1);
16 | --b-full: rgba(204, 102, 102, 1);
17 | --o-full: rgba(102, 204, 102, 1);
18 | }
19 |
20 | #cctContainer h4 {
21 | font-size: 14px;
22 | margin-left: 7px;
23 | margin-bottom: 0.05em;
24 | }
25 |
26 | .cct #cctContainer {
27 | padding: 0.3em;
28 | }
29 | .cct #theSvg {
30 | overflow-x: auto;
31 | width: 100%;
32 | }
33 | .cct table {
34 | margin-bottom: 0;
35 | }
36 | .cct td {
37 | padding: 0;
38 | }
39 |
40 | .cct #cctOptions {
41 | cursor: pointer;
42 | }
43 | .cct legend {
44 | font-size: 110%;
45 | float: none;
46 | width: auto;
47 | display: table;
48 | margin-bottom: 0;
49 | }
50 |
51 | .cct fieldset {
52 | font-size: 90%;
53 | /* line-height: 1; */
54 | display: block;
55 | margin-inline-start: 2px;
56 | margin-inline-end: 2px;
57 | padding-block-start: 0.35em;
58 | padding-inline-start: 0.75em;
59 | padding-inline-end: 0.75em;
60 | padding-block-end: 0;
61 | min-inline-size: min-content;
62 | border-width: 2px;
63 | border-style: groove;
64 | border-color: rgb(192, 192, 192);
65 | border-image: initial;
66 | border-radius: 5px;
67 | margin-bottom: 10px;
68 | }
69 | .cct fieldset input {
70 | margin: 0;
71 | }
72 | .cct fieldset label {
73 | margin-right: 0;
74 | }
75 | .cct fieldset label.left {
76 | margin-left: 10px;
77 | }
78 | .cct fieldset label.right {
79 | margin-right: 10px;
80 | }
81 |
82 | .cct circle.node {
83 | fill: #fff;
84 | stroke-width: 3px;
85 | }
86 |
87 | .cct polygon.node {
88 | cursor: pointer;
89 | }
90 |
91 | .cct circle.node.male {
92 | stroke: var(--a-colour);
93 | }
94 | .cct circle.node.male.birthPlaced {
95 | stroke: var(--a-full);
96 | }
97 |
98 | .cct circle.node.female {
99 | stroke: var(--b-colour);
100 | }
101 | .cct circle.node.female.birthPlaced {
102 | stroke: var(--b-full);
103 | }
104 |
105 | .cct circle.node.other {
106 | stroke: var(--o-colour);
107 | }
108 | .cct circle.node.other.birthPlaced {
109 | stroke: var(--o-full);
110 | }
111 |
112 | .cct circle.node.ofinterest {
113 | stroke: coral;
114 | }
115 |
116 | .cct circle.node.end {
117 | stroke: red;
118 | }
119 |
120 | .cct .node rect.dup.marked {
121 | stroke-width: 3px;
122 | stroke: black;
123 | }
124 |
125 | .cct .treeHeader,
126 | .cct .children-list,
127 | .cct .alt-spouse-list-wrapper,
128 | .cct .node text {
129 | font: 12px sans-serif;
130 | }
131 |
132 | .cct text.marked {
133 | font-size: 14px;
134 | font-weight: bold;
135 | }
136 |
137 | .cct .cctlink {
138 | fill: none;
139 | stroke-width: 2px;
140 | }
141 |
142 | .cct .cctlink.ofinterest {
143 | stroke: lightcoral;
144 | }
145 |
146 | .cct .cctlink.marked {
147 | stroke: green;
148 | }
149 |
150 | .cct .links line {
151 | stroke: #999;
152 | stroke-opacity: 0.6;
153 | }
154 |
155 | .cct .nodes circle {
156 | stroke: #fff;
157 | stroke-width: 1.5px;
158 | }
159 |
160 | .cct #cctBrickWallColour,
161 | .cct #cctLinkLineColour {
162 | width: 2em;
163 | padding: 2px;
164 | }
165 |
166 | .cct #edgeFactor,
167 | .cct #cctTHFactor {
168 | width: 4em;
169 | }
170 |
171 | .cct #help-text {
172 | display: none;
173 | border: 3px solid forestgreen;
174 | border-radius: 1em;
175 | padding: 0.3em 1em;
176 | margin: 1em auto;
177 | position: absolute;
178 | top: 20px;
179 | right: 100px;
180 | width: 50em;
181 | background: white;
182 | box-shadow: 1em 1em 1em grey;
183 | z-index: 11000;
184 | }
185 |
186 | .cct xx {
187 | position: absolute;
188 | top: 0.2em;
189 | right: 0.6em;
190 | font-size: 1em;
191 | cursor: pointer;
192 | font-weight: bold;
193 | color: red;
194 | }
195 | .cct x {
196 | position: absolute;
197 | top: 0em;
198 | left: 0.5em;
199 | /* font-size: 1em; */
200 | cursor: pointer;
201 | font-weight: bold;
202 | color: red;
203 | }
204 |
205 | .cct .pop-up:hover {
206 | cursor: grab;
207 | }
208 | .cct .pop-up:active {
209 | cursor: grabbing;
210 | }
211 | .cct #help-text h3 {
212 | font-size: 1.3em;
213 | margin-bottom: 0.5em;
214 | }
215 |
216 | .cct .spouse-list ol,
217 | .cct .children-list ol {
218 | border: none;
219 | margin: 6px;
220 | padding-top: 0px;
221 | padding-left: 20px;
222 | text-align: left;
223 | --content-padding: 0;
224 | }
225 | .cct .spouse-list li,
226 | .cct .children-list li {
227 | margin-bottom: 0;
228 | }
229 | .cct .children-list,
230 | .cct .alt-spouse-list-wrapper {
231 | background: white;
232 | border-radius: 1em;
233 | border: 3px solid forestgreen;
234 | box-shadow: 0.5em 0.5em 0.5em 0.5em #ccc;
235 | display: none;
236 | position: absolute;
237 | padding: 1.5em 1em 1em;
238 | /* white-space: nowrap; */
239 | z-index: 10000;
240 | }
241 |
242 | .cct .select-spouse-button {
243 | font-size: 14px;
244 | margin: 0;
245 | text-transform: none;
246 | padding: 1px 2px;
247 | color: green;
248 | background-color: transparent;
249 | }
250 |
251 | .cct .select-spouse-button:hover {
252 | cursor: pointer;
253 | /* color: #fc7171; */
254 | }
255 |
256 | .cct .a-radio {
257 | background-color: var(--a-colour);
258 | border: 2px solid var(--a-colour);
259 | }
260 |
261 | .cct .b-radio {
262 | background-color: var(--b-colour);
263 | border: 2px solid var(--b-colour);
264 | }
265 |
266 | .cct .gray-radio {
267 | background-color: darkgray;
268 | border: 2px solid darkgray;
269 | }
270 |
271 | .cct input[type="radio"] {
272 | appearance: none;
273 | width: 15px;
274 | height: 15px;
275 | border-radius: 50%;
276 | background-color: white;
277 | opacity: 1;
278 | &:checked::before {
279 | content: "";
280 | display: block;
281 | width: 5px;
282 | height: 5px;
283 | border-radius: 50%;
284 | background-color: currentColor;
285 | position: relative;
286 | transform: translate(50%, 50%);
287 | }
288 | }
289 |
290 | @media print {
291 | .cct-not-printable {
292 | display: none;
293 | }
294 | }
295 |
296 | @media only screen and (max-width: 767px) {
297 | #cctContainer .ui-draggable-handle {
298 | -ms-touch-action: auto !important;
299 | touch-action: auto !important;
300 | }
301 | #cctContainer #ct-help-text {
302 | position: relative !important;
303 | width: 70% !important;
304 | margin: auto !important;
305 | top: auto;
306 | right: auto;
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/views/compactCouplesTree/cct_view.js:
--------------------------------------------------------------------------------
1 | import { PeopleCache } from "../couplesTree/people_cache.js";
2 | import { CacheLoader } from "../couplesTree/cache_loader.js";
3 | import { CachedPerson } from "../couplesTree/cached_person.js";
4 | import { CCTE } from "./cct_explorer.js";
5 |
6 | window.CCTView = class CCTView extends View {
7 | static #DESCRIPTION = "";
8 | static WANTED_PRIMARY_FIELDS = [
9 | "BirthDate",
10 | "BirthDateDecade",
11 | "DeathDateDecade",
12 | "BirthLocation",
13 | "DataStatus",
14 | "DeathDate",
15 | "DeathLocation",
16 | "Derived.BirthName",
17 | "Derived.BirthNamePrivate",
18 | "Father",
19 | "FirstName",
20 | "Gender",
21 | "HasChildren",
22 | "IsLiving",
23 | "Id",
24 | "LastNameAtBirth",
25 | "LastNameCurrent",
26 | "MiddleInitial",
27 | "Mother",
28 | "Name",
29 | "Privacy",
30 | "Suffix",
31 | ];
32 | constructor() {
33 | super();
34 | CachedPerson.init(new PeopleCache(new CacheLoader(CCTView.WANTED_PRIMARY_FIELDS)));
35 | }
36 |
37 | meta() {
38 | return {
39 | title: "Compact Couples Tree",
40 | description: CCTView.#DESCRIPTION,
41 | docs: "",
42 | };
43 | }
44 |
45 | init(container_selector, person_id) {
46 | // If we need to clear the cache whenever the focus of the tree changes, uncomment the next line
47 | // this.#peopleCache.clear();
48 | wtViewRegistry.setInfoPanel(CCTView.#DESCRIPTION);
49 | wtViewRegistry.showInfoPanel();
50 | const ccte = new CCTE(container_selector).loadAndDraw(person_id);
51 | }
52 |
53 | close() {
54 | $("#svgContainer").off();
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/views/couplesTree/cache_loader.js:
--------------------------------------------------------------------------------
1 | import { CachedPerson } from "./cached_person.js";
2 |
3 | export class CacheLoader {
4 | static APP_ID = "CouplesTree";
5 | static DEFAULT_PRIMARY_FIELDS = [
6 | "BirthDate",
7 | "BirthLocation",
8 | "DataStatus",
9 | "DeathDate",
10 | "DeathLocation",
11 | "Derived.BirthName",
12 | "Derived.BirthNamePrivate",
13 | "Father",
14 | "FirstName",
15 | "Gender",
16 | "HasChildren",
17 | "Id",
18 | "LastNameAtBirth",
19 | "LastNameCurrent",
20 | "MiddleInitial",
21 | "Mother",
22 | "Name",
23 | "Photo",
24 | "Suffix",
25 | ];
26 | static DEFAULT_VARIABLE_FIELDS = ["Parents", "Spouses", "Children"];
27 |
28 | #allFields = [...new Set([...CacheLoader.DEFAULT_PRIMARY_FIELDS, ...CacheLoader.DEFAULT_VARIABLE_FIELDS])];
29 |
30 | #primaryFields;
31 | #variableFields;
32 |
33 | /**
34 | * Construct the loader with two sets of fields to load:
35 | *
36 | * @param {*} primaryFields (optional) fields that will always be retrieved.
37 | * See CacheLoader.DEFAULT_PRIMARY_FIELDS for the default
38 | * @param {*} variableFields (optional) additional fields that will be retrieved by default, but the user
39 | * can change these per get() call. The default value is ["Parents", "Spouses", "Children"]
40 | */
41 | constructor(primaryFields, variableFields) {
42 | this.#primaryFields =
43 | typeof primaryFields === "undefined" ? CacheLoader.DEFAULT_PRIMARY_FIELDS : [...new Set(primaryFields)];
44 | this.#variableFields =
45 | typeof variableFields === "undefined" ? CacheLoader.DEFAULT_VARIABLE_FIELDS : [...new Set(variableFields)];
46 | this.#allFields = [...new Set([...this.#primaryFields, ...this.#variableFields])];
47 | }
48 |
49 | /**
50 | * Retrieve the indicated profile using getPerson. By default all the fields (primary and variable) specified
51 | * in the constructor will be retrieved.
52 | * @param {*} id The profile id to retrieve
53 | * @param {*} requestedVariableFields (optional) If not specified, all the primary and variable fields defined
54 | * in the constructor, plus any additionalFields specfied, will be retrieved. If specified, these
55 | * fields will replace the set of variable fields specified in the constructor.
56 | * @param {*} additionalFields (optional) Additional fields to be included in the retrieval.
57 | * @returns
58 | */
59 | async get(id, requestedVariableFields, additionalFields) {
60 | if (!additionalFields && !requestedVariableFields) {
61 | return await this.getPersonViaAPI(id, this.#allFields);
62 | } else {
63 | return await this.getPersonViaAPI(id, [
64 | ...new Set([
65 | ...this.#primaryFields,
66 | ...(requestedVariableFields ? requestedVariableFields : CacheLoader.DEFAULT_VARIABLE_FIELDS),
67 | ...(additionalFields ? additionalFields : []),
68 | ]),
69 | ]);
70 | }
71 | }
72 |
73 | /**
74 | * To get a Person for a given id, we POST to the API's getPerson action. When we get a result back,
75 | * we convert the returned JSON data into a Person object.
76 | * Note that postToAPI returns the Promise from Javascript's fetch() call.
77 | * This function returns a Promise (as result of the await), which gets resolved after the await.
78 | * So we can use this through asynchronous actions with something like:
79 | * getPersonViaAPI.then(function(result) {
80 | * // the "result" here is a new Person object.
81 | * });
82 | * or
83 | * newPerson = await getPersonViaAPI(id, fields);
84 | *
85 | * @param id The WikiTree ID of the person to retrieve
86 | * @param fields An array of field names to retrieve for the given person
87 | * @returns a Promise
88 | */
89 | async getPersonViaAPI(id, fields) {
90 | const result = await WikiTreeAPI.postToAPI({
91 | appId: CacheLoader.APP_ID,
92 | action: "getPerson",
93 | key: id,
94 | fields: fields.join(","),
95 | resolveRedirect: 1,
96 | });
97 | return new CachedPerson(result[0].person);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/views/couplesTree/couples_tree.css:
--------------------------------------------------------------------------------
1 | #couples-view-container {
2 | --content-padding: 0;
3 | position: relative;
4 | }
5 | #couples-view-container:hover {
6 | cursor: grab;
7 | }
8 | #couples-view-container:active {
9 | cursor: grabbing;
10 | }
11 |
12 | #couples-view-container h4 {
13 | font-size: 14px;
14 | }
15 |
16 | .cvtc .couple .box {
17 | cursor: pointer;
18 | border-style: solid;
19 | border-width: 2px;
20 | border-radius: 5px;
21 | text-align: center;
22 | padding: 5px;
23 | background-color: #fff;
24 | margin: 0;
25 | }
26 |
27 | .cvtc .box[data-gender="male"],
28 | .cvtc .popup-box[data-gender="male"] {
29 | border-color: rgba(102, 102, 204, 0.5);
30 | }
31 |
32 | .cvtc .descendant .box[data-gender="male"][data-infocus="true"],
33 | .cvtc .ancestor.root .box[data-gender="male"][data-infocus="true"] {
34 | cursor: pointer;
35 | border-color: rgba(102, 102, 204, 0.5);
36 | background-image: linear-gradient(
37 | to top right,
38 | rgba(102, 102, 204, 0.3),
39 | rgba(102, 102, 204, 0.05),
40 | rgba(102, 102, 204, 0)
41 | );
42 | }
43 |
44 | .cvtc .box[data-gender="female"],
45 | .cvtc .popup-box[data-gender="female"] {
46 | border-color: rgba(204, 102, 102, 0.5);
47 | }
48 |
49 | .cvtc .descendant .box[data-gender="female"][data-infocus="true"],
50 | .cvtc .ancestor.root .box[data-gender="female"][data-infocus="true"] {
51 | border-color: rgba(204, 102, 102, 0.5);
52 | background-image: linear-gradient(
53 | to top right,
54 | rgba(204, 102, 102, 0.3),
55 | rgba(204, 102, 102, 0.05),
56 | rgba(204, 102, 102, 0)
57 | );
58 | }
59 |
60 | .cvtc .box,
61 | .cvtc .popup-box {
62 | border-color: rgba(102, 204, 102, 0.5);
63 | }
64 |
65 | .cvtc .descendant .box[data-infocus="true"] {
66 | border-color: rgba(102, 204, 102, 0.5);
67 | background-image: linear-gradient(
68 | to top right,
69 | rgba(102, 204, 102, 0.2),
70 | rgba(102, 204, 102, 0),
71 | rgba(102, 204, 102, 0)
72 | );
73 | }
74 |
75 | .cvtc .person .cname {
76 | font-size: 14px;
77 | }
78 |
79 | .cvtc .person .marriage-date {
80 | font-size: 12px;
81 | }
82 |
83 | .cvtc .button-words {
84 | color: black;
85 | }
86 |
87 | .cvtc .drop-button {
88 | font-size: 14px;
89 | line-height: 0;
90 | margin: 0;
91 | text-transform: none;
92 | padding: 1px 2px;
93 | color: green;
94 | background-color: transparent;
95 | border-color: transparent;
96 | }
97 |
98 | .cvtc .select-spouse-button {
99 | font-size: 14px;
100 | margin: 0;
101 | text-transform: none;
102 | padding: 1px 2px;
103 | color: green;
104 | background-color: transparent;
105 | }
106 |
107 | .cvtc .select-spouse-button:hover,
108 | .cvtc .drop-button:hover {
109 | cursor: pointer;
110 | /* color: #fc7171; */
111 | }
112 |
113 | .cvtc .altSpouse {
114 | font-size: 14px;
115 | }
116 |
117 | .cvtc .alt-spouse-list-wrapper h4 {
118 | margin: 12px;
119 | }
120 |
121 | .cvtc .spouse-list ol,
122 | .cvtc .children-list ol {
123 | border: none;
124 | margin: 6px;
125 | padding-top: 0px;
126 | padding-left: 20px;
127 | text-align: left;
128 | --content-padding: 0;
129 | }
130 | .cvtc .spouse-list li,
131 | .cvtc .children-list li {
132 | margin: 6px;
133 | }
134 | .cvtc .children-list-wrapper h4 {
135 | margin: 12px;
136 | background: #f9f9f9;
137 | }
138 |
139 | .cvtc .children-list ol {
140 | margin: 0;
141 | }
142 |
143 | .cvtc .children-list {
144 | font-size: 14px;
145 | border-radius: 5px;
146 | border: solid rgba(102, 204, 102, 0.5);
147 | background: #f9f9f9;
148 | }
149 |
150 | .cvtc .minus {
151 | fill: #fff;
152 | cursor: pointer;
153 | stroke: #333;
154 | stroke-width: 1px;
155 | }
156 |
157 | .cvtc .minus:hover {
158 | stroke: #fcbb71;
159 | }
160 |
161 | .cvtc #ct-help-button {
162 | background: #393a3c;
163 | color: #fff;
164 | font-size: 0.8em;
165 | padding: 0.1em 0.5em;
166 | border-radius: 50%;
167 | float: right;
168 | font-weight: bold;
169 | cursor: pointer;
170 | position: absolute;
171 | top: 0.2em;
172 | right: 0.2em;
173 | z-index: 11000;
174 | }
175 |
176 | .cvtc xx {
177 | position: absolute;
178 | top: 0.2em;
179 | right: 0.6em;
180 | font-size: 1em;
181 | cursor: pointer;
182 | font-weight: bold;
183 | color: red;
184 | }
185 |
186 | .cvtc #ct-help-text {
187 | display: none;
188 | border: 3px solid forestgreen;
189 | border-radius: 1em;
190 | padding: 0.3em 1em;
191 | margin: 1em auto;
192 | position: absolute;
193 | top: 20px;
194 | right: 100px;
195 | width: 45em;
196 | background: white;
197 | box-shadow: 1em 1em 1em grey;
198 | padding-right: 4em;
199 | z-index: 11000;
200 | cursor: default;
201 | }
202 | .cvtc #ct-help-text:hover {
203 | cursor: grab;
204 | }
205 | .cvtc #ct-help-text:active {
206 | cursor: grabbing;
207 | }
208 |
209 | @media only screen and (max-width: 767px) {
210 | #couples-view-container .ui-draggable-handle {
211 | -ms-touch-action: auto !important;
212 | touch-action: auto !important;
213 | }
214 | #couples-view-container #ct-help-text {
215 | position: relative !important;
216 | width: 70% !important;
217 | margin: auto !important;
218 | top: auto;
219 | right: auto;
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/views/couplesTree/people_cache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A cache for People objects.
3 | */
4 |
5 | import { CachedPerson, isSameOrHigherRichness } from "./cached_person.js";
6 |
7 | export class PeopleCache {
8 | #cache;
9 | #loader;
10 | constructor(loader) {
11 | this.#cache = new Map();
12 | this.#loader = loader;
13 | condLog("NEW PEOPLE CACHE CREATED");
14 | }
15 |
16 | clear() {
17 | this.#cache.clear();
18 | condLog("PEOPLE CACHE CLEARED");
19 | }
20 |
21 | getIfPresent(id) {
22 | return this.#cache.get(+id);
23 | }
24 |
25 | async getWithLoad(id, mustHaveFields, additionalFields) {
26 | const pId = +id;
27 | //condLog(`getWithLoad ${pId}`, [...this.#cache.keys()]);
28 | let cachedPerson = this.#cache.get(pId);
29 | if (cachedPerson && cachedPerson.hasFields(mustHaveFields)) {
30 | condLog(`getWithLoad from cache ${cachedPerson.toString()}`);
31 | return new Promise((resolve, reject) => {
32 | resolve(cachedPerson);
33 | });
34 | }
35 | if (this.#loader) {
36 | const newPerson = await this.#loader.get(pId, mustHaveFields, additionalFields);
37 | cachedPerson = this.#cache.get(pId);
38 | if (cachedPerson) {
39 | if (isSameOrHigherRichness(newPerson._data, cachedPerson._data)) {
40 | condLog(`getWithLoad updating cache: ${newPerson.toString()}`);
41 | cachedPerson._data = newPerson._data;
42 | } else {
43 | condLog(`getWithLoad from cache: ${cachedPerson.toString()} richer than ${newPerson.toString()}`);
44 | }
45 | } else {
46 | condLog(`getWithLoad adding to cache: ${newPerson.toString()}`);
47 | this.#cache.set(pId, newPerson);
48 | cachedPerson = newPerson;
49 | }
50 | }
51 | return cachedPerson;
52 | }
53 |
54 | /**
55 | * If an enriched person with the given id (i.e. data.id) exists in the cache and it has at least
56 | * the same level of richness than the given data, the cached Person is returned, otherwise
57 | * the data is used to create a new CachedPerson and add it to the cache, replacing any previous
58 | * person with the same id.
59 | * @param {*} data
60 | * @returns
61 | */
62 | getWithData(data) {
63 | const id = +data.Id;
64 | //condLog(`getWithData ${id}`, [...this.#cache.keys()]);
65 | let cachedPerson = this.#cache.get(id);
66 | if (cachedPerson && isSameOrHigherRichness(cachedPerson._data, data)) {
67 | condLog(`getWithData from cache ${cachedPerson.toString()}`);
68 | //TODO: should we update cachedPerson with any new data??
69 | return cachedPerson;
70 | }
71 | const newPerson = new CachedPerson(data);
72 | cachedPerson = this.#cache.get(id);
73 | if (cachedPerson) {
74 | condLog(`getWithData updating cache: ${newPerson.toString()}`);
75 | cachedPerson._data = newPerson._data;
76 | } else {
77 | condLog(`getWithData adding to cache: ${newPerson.toString()}`);
78 | this.#cache.set(id, newPerson);
79 | cachedPerson = newPerson;
80 | }
81 | return cachedPerson;
82 | }
83 |
84 | has(id) {
85 | return this.#cache.has(id);
86 | }
87 |
88 | isRequestCoveredByPerson(reqFields, person) {
89 | const reqRichness = Richness.fromFields(reqFields);
90 | return (person.getRichness() & reqRichness) == reqRichness;
91 | }
92 |
93 | getMap() {
94 | return this.#cache;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/views/familyCalendar/calendar.css:
--------------------------------------------------------------------------------
1 | #display-mode {
2 | padding: 5px;
3 | width: 300px;
4 | }
5 |
6 | #watchCalendar .calendar {
7 | display: none;
8 | height: 100%;
9 | }
10 |
11 | .loaderIMG {
12 | width: 100px;
13 | }
14 |
15 | #watchCalendar .loader {
16 | width: 101px;
17 | position: relative;
18 | -webkit-animation: spin 4s linear infinite;
19 | -moz-animation: spin 4s linear infinite;
20 | animation: spin 4s linear infinite;
21 | left: 45%;
22 | top: 45%;
23 | }
24 |
25 | @-moz-keyframes spin {
26 | 100% {
27 | -moz-transform: rotate(360deg);
28 | }
29 | }
30 |
31 | @-webkit-keyframes spin {
32 | 100% {
33 | -webkit-transform: rotate(360deg);
34 | }
35 | }
36 |
37 | @keyframes spin {
38 | 100% {
39 | -webkit-transform: rotate(360deg);
40 | transform: rotate(360deg);
41 | }
42 | }
43 |
44 | #watchCalendar .content {
45 | padding: 0 18px;
46 | display: none;
47 | overflow: hidden;
48 | background-color: #f1f1f1;
49 | width: 47%;
50 | margin: auto;
51 | }
52 |
53 | span[id^="month-"] {
54 | padding: 0 5px;
55 | font-size: 1.3em;
56 | color: #060;
57 | font-weight: bold;
58 | text-decoration: underline;
59 | cursor: pointer;
60 | }
61 |
62 | div.fourteen.columns.center {
63 | margin-bottom: 10px;
64 | overflow-wrap: break-word;
65 | }
66 |
--------------------------------------------------------------------------------
/views/familyCalendar/version.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [Unreleased]
6 | ### Added
7 | - Added Head > Left view options, Week and Day list views.
8 |
9 | ### Changed
10 | - Event URL's now go to Relationship Finder instead of direct to profile.
11 | - Moved Print Calendar to Head > Right.
12 |
13 | ### Fixed
14 | - Event colors now consistent across event types for cross-device compatibility.
15 |
16 | ## [1.0.0] - 2023-08-23
17 | ### Added
18 | - Initial version of the Family Calendar feature.
19 |
20 | [Unreleased]: https://apps.wikitree.com/apps/harris5439/wikitree-dynamic-tree/
21 | [1.0.0]: https://www.wikitree.com/apps/&view=familyCalendar
--------------------------------------------------------------------------------
/views/familyGroupApp/images/print50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/familyGroupApp/images/print50.png
--------------------------------------------------------------------------------
/views/familyGroupApp/images/tree.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/familyGroupApp/images/tree.gif
--------------------------------------------------------------------------------
/views/familyGroupApp/js/handleLinks.js:
--------------------------------------------------------------------------------
1 | export class HandleLinks {
2 | fixLinks() {
3 | $("#view-container a:not([href^='#'])").each(function () {
4 | const $this = $(this);
5 | const href = $this.attr("href");
6 | //console.log("Processing link: ", href); // Debugging
7 |
8 | // Find links without a domain and add the domain
9 | if (href && href.match(/http/) == null && !href.startsWith("#")) {
10 | $this.attr("href", "https://www.wikitree.com" + href);
11 | //console.log("Updated link: ", $this.attr("href")); // Debugging
12 | }
13 | });
14 | }
15 |
16 | fixImageLinks() {
17 | $("#view-container img").each(function () {
18 | const $this = $(this);
19 | const src = $this.attr("src");
20 | // Find links without a domain and add the domain
21 | if (src && !src.match(/http/) && !src.match(/familyGroupApp/)) {
22 | $this.attr("src", "https://www.wikitree.com" + src);
23 | }
24 | });
25 | }
26 |
27 | addIdToReferences(dummyDiv, Id) {
28 | dummyDiv.find("li[id^='_note'] a[href^='#'],sup,ol.references li[id^='_note']").each(function () {
29 | const el = $(this);
30 | const id = el.prop("id");
31 | if (id && !id.includes(Id)) {
32 | const newId = id + "_" + Id;
33 | el.prop("id", newId);
34 | }
35 | if (el[0].tagName === "SUP") {
36 | el.find("a").each(function () {
37 | const a = $(this);
38 | const href = a.attr("href");
39 | if (href && !href.includes(Id)) {
40 | const newHref = href + "_" + Id;
41 | a.attr("href", newHref);
42 | }
43 | });
44 | } else if (el[0].tagName === "A") {
45 | const href = el.attr("href");
46 | if (href && !href.includes(Id)) {
47 | const newHref = href + "_" + Id;
48 | el.attr("href", newHref);
49 | }
50 | }
51 | });
52 | }
53 | openLinksInNewTab() {
54 | $("#view-container a:not([href^='#'])").attr("target", "_blank");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/views/familyGroupApp/js/personDataCache.js:
--------------------------------------------------------------------------------
1 | export class PersonDataCache {
2 | constructor(maxSize = 50) {
3 | this.cache = new Map();
4 | this.maxSize = maxSize;
5 | }
6 |
7 | getPersonData(id) {
8 | const data = this.cache.get(id);
9 | return data;
10 | }
11 |
12 | setPersonData(id, data) {
13 | if (!this.cache.has(id)) {
14 | if (this.cache.size >= this.maxSize) {
15 | const firstKey = this.cache.keys().next().value;
16 | this.cache.delete(firstKey);
17 | console.log(`[Cache] Cache limit reached. Evicting oldest entry: ${firstKey}`);
18 | }
19 | this.cache.set(id, data);
20 | console.log(`[Cache] Setting data for ${id}`);
21 | } else {
22 | console.log(`[Cache] Data for ${id} already exists in cache. Not updating.`);
23 | }
24 | }
25 |
26 | // This method can be used to inspect the current state of the cache.
27 | logCacheState() {
28 | console.log("Current cache state:", Array.from(this.cache.entries()));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/views/familyPortraits/portraits.css:
--------------------------------------------------------------------------------
1 | .portrait-gallery-image {
2 | height: 150px;
3 | /* Optional: You can also specify other CSS properties like margin or padding here. */
4 | }
--------------------------------------------------------------------------------
/views/familyPortraits/portraits.js:
--------------------------------------------------------------------------------
1 | window.PortraitView = class PortraitView extends View {
2 | static APP_ID = "Portraits";
3 |
4 | constructor() {
5 | super();
6 | this.abortController = new AbortController();
7 | }
8 |
9 | meta() {
10 | return {
11 | title: "Family Portraits",
12 | description: `Generate a photo gallery that displays portraits of ancestors. Click an image to visit the profile.`,
13 | docs: "",
14 | params: ["ancestors", "siblings"]
15 | };
16 | }
17 |
18 | close() {
19 | if (this.abortController) {
20 | this.abortController.abort();
21 | console.log('API processing halted.');
22 | }
23 | $('#view-options').remove();
24 | }
25 |
26 | init(selector, person_id, params) {
27 | this.abortController = new AbortController();
28 |
29 | const ancestors = params.ancestors || 10;
30 | const validSiblings = (siblings) => ['1', '0'].includes(siblings) ? siblings : '1';
31 | const siblings = validSiblings(params.siblings);
32 |
33 | var pageNumber = 0;
34 | this.getPages(pageNumber, selector, person_id, ancestors, siblings);
35 | }
36 |
37 | getPages(pageNumber, selector, person_id, ancestors, siblings) {
38 | WikiTreeAPI.postToAPI({
39 | appId: PortraitView.APP_ID,
40 | action: "getPeople",
41 | keys: person_id,
42 | ancestors: ancestors,
43 | siblings: siblings,
44 | fields: "Name,PhotoData",
45 | limit: 1000,
46 | start: pageNumber,
47 | signal: this.abortController.signal // Use the signal to allow aborting
48 | }).then((data) => {
49 | if (this.abortController.signal.aborted) {
50 | console.log('Request was aborted. Stopping further processing.');
51 | return;
52 | }
53 |
54 | const errorMessage = "Ancestor/Descendant permission denied.";
55 | if (
56 | data[0].resultByKey &&
57 | data[0].resultByKey[person_id] &&
58 | data[0].resultByKey[person_id].status === errorMessage
59 | ) {
60 | let err = `The starting profile data could not be retrieved.`;
61 | if (wtViewRegistry?.session.lm.user.isLoggedIn()) {
62 | err += ` You may need to be added to the starting profile's Trusted List.`;
63 | } else {
64 | err += ` Try the Apps login.`;
65 | }
66 | wtViewRegistry.showError(err);
67 | wtViewRegistry.hideInfoPanel();
68 | } else {
69 | const photoDataList = [];
70 | const galleryContainer = $(selector);
71 | const newGalleryDiv = $('');
72 |
73 | for (const key in data[0].people) {
74 | if (data[0].people.hasOwnProperty(key)) {
75 | const person = data[0].people[key];
76 | const name = person.Name;
77 | const url = person.PhotoData ? person.PhotoData.url : null;
78 | const path = person.PhotoData ? person.PhotoData.path : null;
79 | const origWidth = person.PhotoData ? person.PhotoData.orig_width : null;
80 | const origHeight = person.PhotoData ? person.PhotoData.orig_height : null;
81 |
82 | if (name && url && origWidth && origHeight) {
83 | const aspectRatio = origWidth / origHeight;
84 |
85 | if (origWidth >= 301) {
86 | if (aspectRatio <= 10.0) {
87 | const new300pxUrl = url.replace('/75px-', '/300px-');
88 | photoDataList.push({ name, url: new300pxUrl });
89 | }
90 | } else {
91 | if (!path.includes('pdf')) {
92 | photoDataList.push({ name, path });
93 | }
94 | }
95 | }
96 | }
97 | }
98 |
99 | if (galleryContainer) {
100 | const galleryHTML = photoDataList.map((data) => `
101 |
102 |
103 |
104 | `).join('');
105 | newGalleryDiv.html(galleryHTML);
106 | galleryContainer.append(newGalleryDiv);
107 | }
108 |
109 | var numPeople = Object.keys(data[0].people).length;
110 | if (numPeople === 1000) {
111 | pageNumber += 1000;
112 | this.getPages(pageNumber, selector, person_id, ancestors, siblings);
113 | }
114 | }
115 | }).catch((error) => {
116 | if (error.name === 'AbortError') {
117 | console.log('API request was aborted.');
118 | } else {
119 | console.error('Error fetching data:', error);
120 | }
121 | });
122 | }
123 | };
124 |
--------------------------------------------------------------------------------
/views/familyView/familyView.css:
--------------------------------------------------------------------------------
1 | #family_group {
2 | background-color: white;
3 | width: 95%;
4 | margin-left: auto;
5 | margin-right: auto;
6 | clear: both;
7 | }
8 |
9 | #family_group h2, #children_list h2 {
10 | text-indent: 5%;
11 | }
12 |
13 | #family_group h3, #children_list h3 {
14 | font-size: 90%;
15 | text-indent: 8%;
16 | }
17 |
18 | #children_list {
19 | background-color: white;
20 | width: 75%;
21 | margin-left: auto;
22 | margin-right: auto;
23 | clear: both;
24 | }
25 |
26 | .fv_singleFamily {
27 | width: 95%;
28 | margin-left: auto;
29 | margin-right: auto;
30 | margin-top: 20px;
31 | margin-bottom: 20px;
32 | clear: both;
33 | }
34 |
35 | .fv_Family {
36 | width: 100%;
37 | border-collapse: collapse;
38 | text-align: center;
39 | justify-content: center;
40 | }
41 |
42 | .fv_Parents td {
43 | width: 50%;
44 | border: #aaaaaa 1px solid;
45 | }
46 |
47 | .fv_Children td {
48 | border-width: 0;
49 | padding: 0;
50 | border-spacing: 0;
51 | }
52 |
53 | .fv_Children_Table {
54 | width: 100%;
55 | }
56 |
57 | .fv_Children_Table tr td {
58 | border: #aaaaaa 1px solid;
59 | }
60 |
61 | .gender_Male {
62 | background-color: #eef;
63 | }
64 |
65 | .gender_Female {
66 | background-color: #fee;
67 | }
68 |
69 | @media print {
70 | :root:has(#family_group) {
71 | header {
72 | display: none !important;
73 | }
74 |
75 | main {
76 | font-size: 90%;
77 | }
78 |
79 | #view-container {
80 | width: 100% !important;
81 | padding: 0 !important;
82 | margin: 0 !important;
83 | border: 0 !important;
84 | font-size: 80%;
85 | }
86 |
87 | .icon {
88 | display: none;
89 | }
90 |
91 | a:link, a:visited, a {
92 | color: black;
93 | text-decoration: none;
94 | }
95 | }
96 |
97 | #family_group {
98 | width: 100% !important;
99 | padding: 0 !important;
100 | margin: 0 !important;
101 | }
102 |
103 | #family_group h2, #children_list h2 {
104 | text-indent: 0;
105 | font-size: 100%;
106 | margin-bottom: 0;
107 | }
108 |
109 | #family_group h3, #children_list h3 {
110 | text-indent: 0;
111 | font-size: 90%;
112 | margin-bottom: 0;
113 | }
114 |
115 | .fv_familyBlock, .fv_Bio {
116 | page-break-inside: avoid;
117 | }
118 |
119 | .fv_singleFamily {
120 | width: 100% !important;
121 | padding: 0 !important;
122 | margin: 0 !important;
123 | }
124 |
125 | .fv_Parents, .fv_Children {
126 | font-size: 80%;
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/views/fanChart/PeopleCollection.js:
--------------------------------------------------------------------------------
1 | /*
2 | * PeopleCollection.js
3 | /*
4 | // **************************************
5 | // A Class to hold a collection of PEOPLE (a collection of the Person objects)
6 | // **************************************
7 | /
8 | * The basic PeopleCollection is an associative array
9 | * key: Id , value: Person object corresponding to that WikiTree ID #
10 | *
11 | *
12 | */
13 |
14 | window.PeopleCollection = window.PeopleCollection || {};
15 |
16 | // Our basic constructor for a PeopleList. We expect the "person" data from the API returned result
17 | // (see getPerson below). The basic fields are just stored in the internal _data array.
18 | // We pull out the Parent and Child elements as their own Person objects.
19 | PeopleCollection.PeopleList = class PeopleList {
20 | constructor(data) {
21 | // Nothing needed for this very simple class, starts off as an empty object
22 | }
23 |
24 | // THEN ... we use the add method to add one person to it, using the WikiTree ID as the key
25 | add(newPerson) {
26 | if (newPerson && newPerson.Id) {
27 | // condLog("Adding: ", newPerson.Id);
28 | this[newPerson.Id] = new WikiTreeAPI.Person(newPerson);
29 | } else {
30 | condLog("Can't add newPerson: ", newPerson);
31 | }
32 | }
33 |
34 | // OR ... we use the add method if it's a new person, else we update the person
35 | addOrUpdate(newPerson) {
36 | if (newPerson && newPerson.Id) {
37 | // condLog("Adding: ", newPerson.Id);
38 | if (!this[newPerson.Id]) {
39 | this.add(newPerson);
40 | } else {
41 | condLog("Need to UPDATE peeps ", newPerson, "in", this[newPerson.Id]);
42 | for (const key in newPerson) {
43 | this[newPerson.Id]._data[key] = newPerson[key];
44 | }
45 | }
46 | } else {
47 | condLog("Can't add newPerson: ", newPerson);
48 | }
49 | }
50 |
51 | // OR ... we only use the add method if it's a new person, else we ignore the request
52 | addIfNeeded(newPerson) {
53 | if (newPerson && newPerson.Id) {
54 | // condLog("Adding: ", newPerson.Id);
55 | if (!this[newPerson.Id]) {
56 | this.add(newPerson);
57 | } else {
58 | condLog("Don't need to add new peeps ", newPerson, " - already there - ", this[newPerson.Id]);
59 | }
60 | } else {
61 | condLog("Can't add newPerson: ", newPerson);
62 | }
63 | }
64 |
65 |
66 | // A simple function to Count all the people in the collection to the condLog (for debugging purposes)
67 | population() {
68 | let numPeeps = 0;
69 |
70 | for (const key in this) {
71 | if (Object.hasOwnProperty.call(this, key)) {
72 | numPeeps++;
73 | }
74 | }
75 | condLog("There are ", numPeeps, " in PeopleCollection");
76 | return numPeeps;
77 | }
78 |
79 | // A simple function to List All people in the collection to the condLog (for debugging purposes)
80 | listAll() {
81 | let numPeeps = 0;
82 |
83 | for (const key in this) {
84 | if (Object.hasOwnProperty.call(this, key)) {
85 | const element = this[key];
86 | // condLog(key );
87 | numPeeps++;
88 | }
89 | }
90 | condLog("There are ", numPeeps, " in PeopleCollection");
91 | }
92 |
93 | // A similar List function to create an Array of Person objects to feed to the d3 Tree function
94 | listAllPersons() {
95 | let numPeeps = 0;
96 | let theListOfPersons = [];
97 | for (const key in this) {
98 | if (Object.hasOwnProperty.call(this, key)) {
99 | const element = this[key];
100 | numPeeps++;
101 | element._data.ahnNum = numPeeps;
102 | theListOfPersons.push(element);
103 | // condLog("Add to list: ",key , element);
104 | }
105 | }
106 | // condLog("There are ",numPeeps," in PeopleCollection");
107 | return theListOfPersons;
108 | }
109 | };
110 |
111 | // CREATE the INSTANCE of the PeopleCollection, call it thePeopleList, and refer to it as such throughout the views that use this
112 | var thePeopleList = new PeopleCollection.PeopleList();
113 |
114 | // condLog(thePeopleList);
115 | // thePeopleList.listAll();
116 |
--------------------------------------------------------------------------------
/views/fanChart/SVGfunctions.js:
--------------------------------------------------------------------------------
1 | /*
2 | // *********
3 | // FUNCTIONS needed to create ARCS / WEDGES / SECTORS etc...
4 | // *********
5 | */
6 |
7 | // Put these functions into a "SVGfunctions" namespace.
8 | window.SVGfunctions = window.SVGfunctions || {};
9 |
10 | // Returns an array [x , y] that corresponds to the endpoint of rθ from (centreX,centreY)
11 | SVGfunctions.polarToCartesian = function (centerX, centerY, radius, angleInDegrees) {
12 | angleInRadians = (angleInDegrees * Math.PI) / 180.0;
13 |
14 | return [centerX + radius * Math.cos(angleInRadians), centerY + radius * Math.sin(angleInRadians)];
15 | // };
16 | };
17 |
18 | // Returns an array of letters and numbers that correspond to g graphics drawing commands to create an Arc
19 | SVGfunctions.describeArc = function (x, y, radius, startAngle, endAngle) {
20 | start = SVGfunctions.polarToCartesian(x, y, radius, endAngle);
21 | end = SVGfunctions.polarToCartesian(x, y, radius, startAngle);
22 |
23 | largeArcFlag = (720 + endAngle - startAngle) % 360 <= 180 ? "0" : "1";
24 | // largeArcFlag = 1;
25 |
26 | d = ["M", start[0], start[1], "A", radius, radius, 0, largeArcFlag, 0, end[0], end[1]];
27 |
28 | return d;
29 | };
30 |
31 | // Returns an attribute object that can be attached to a object to create an Arc
32 | // IF the Arc is being redefined, only the "d" attribute need be updated
33 | // Note: an Arc is simply a curved line, a piece of a circle
34 | SVGfunctions.getSVGforArc = function (x, y, radius, startAngle, endAngle, id = "arc", clr = "blue", thickness = 2) {
35 | fillClr = "none";
36 | if (startAngle == endAngle) {
37 | startAngle = 0.0001;
38 | endAngle = -0.0001;
39 | }
40 | theSVGpath = "";
41 | arc = SVGfunctions.describeArc(x, y, radius, startAngle, endAngle);
42 |
43 | for (i = 0; i < arc.length; i++) {
44 | theSVGpath += arc[i] + " ";
45 | }
46 |
47 | let tempAttributes = {
48 | "id": id,
49 | "fill": fillClr,
50 | "stroke": clr,
51 | "stroke-width": thickness,
52 | "d": theSVGpath,
53 | };
54 | return tempAttributes;
55 | };
56 |
57 | // Returns an attribute object that can be attached to a object to create an Sector
58 | // IF the Sector is being redefined, only the "d" attribute need be updated
59 | // Note: a Sector is a 2 dimensional area, with 2 radii and an Arc in between (ie - it's pointy at the centre of the circle it emanates from, defined by x,y )
60 | SVGfunctions.getSVGforSector = function (
61 | x,
62 | y,
63 | radius,
64 | startAngle,
65 | endAngle,
66 | id = "arc",
67 | clr = "blue",
68 | thickness = 2,
69 | fillClr = "none"
70 | ) {
71 | if (startAngle == endAngle) {
72 | startAngle = 0.0001;
73 | endAngle = -0.0001;
74 | }
75 | theSVGpath = "";
76 | arc = SVGfunctions.describeArc(x, y, radius, startAngle, endAngle);
77 |
78 | for (i = 0; i < arc.length; i++) {
79 | theSVGpath += arc[i] + " ";
80 | }
81 | theSVGpath += " L " + x + " " + y + " ";
82 | start = SVGfunctions.polarToCartesian(x, y, radius, endAngle);
83 | theSVGpath += " L " + start[0] + " " + start[1] + " ";
84 |
85 | let tempAttributes = {
86 | "id": id,
87 | "fill": fillClr,
88 | "stroke": clr,
89 | "stroke-width": thickness,
90 | "d": theSVGpath,
91 | };
92 | return tempAttributes;
93 | };
94 | // Returns an attribute object that can be attached to a object to create an Wedge
95 | // IF the Wedge is being redefined, only the "d" attribute need be updated
96 | // Note 1: the radius parameter refers to the OUTER edge of the Wedge, furthest from the centre.
97 | // Note 2: the wedgeRadius parameter refers to the INNER edge of the Wedge, closest to the centre.
98 | // Note 3: a Wedge is a 2 dimensional area, with an Arc for its outer edge, 2 partial radii for its side, and a straight line for its inner edge, closest to the centre of the circle at x,y (It is not pointy)
99 | SVGfunctions.getSVGforWedge = function (
100 | x,
101 | y,
102 | radius,
103 | wedgeRadius,
104 | startAngle,
105 | endAngle,
106 | id = "arc",
107 | clr = "blue",
108 | thickness = 2,
109 | fillClr = "none"
110 | ) {
111 | if (startAngle == endAngle) {
112 | startAngle = 0.0001;
113 | endAngle = -0.0001;
114 | }
115 | theSVGpath = "";
116 | arc = SVGfunctions.describeArc(x, y, radius, startAngle, endAngle);
117 |
118 | for (i = 0; i < arc.length; i++) {
119 | theSVGpath += arc[i] + " ";
120 | }
121 | // theSVGpath .= " L x y ";
122 | endWedge = SVGfunctions.polarToCartesian(x, y, radius - wedgeRadius, endAngle);
123 | startWedge = SVGfunctions.polarToCartesian(x, y, radius - wedgeRadius, startAngle);
124 | startPoint = SVGfunctions.polarToCartesian(x, y, radius, endAngle);
125 | theSVGpath += " L " + startWedge[0] + " " + startWedge[1] + " ";
126 | theSVGpath += " L " + endWedge[0] + " " + endWedge[1] + " ";
127 | theSVGpath += " L " + startPoint[0] + " " + startPoint[1] + " ";
128 |
129 | let tempAttributes = {
130 | "id": id,
131 | "fill": fillClr,
132 | "stroke": clr,
133 | "stroke-width": thickness,
134 | "d": theSVGpath,
135 | };
136 | return tempAttributes;
137 | };
138 |
139 | // EXAMPLE uses for these functions
140 | // svg.append("path")
141 | // .attr( SVGfunctions.getSVGforArc(0, 0, 200, 45, 90, 'arc0n0', 'red', 3) );
142 | // svg.append("path")
143 | // .attr( SVGfunctions.getSVGforSector(0, 0, 300, 145, 190, 'sect0n0', 'blue', 4, 'pink') );
144 | // svg.append("path")
145 | // .attr( SVGfunctions.getSVGforWedge(0, 0, 500, 300, 245, 290, 'wedge0n0', 'purple', 6, 'lime') );
146 |
--------------------------------------------------------------------------------
/views/fanChart/WTapps_Utils.js:
--------------------------------------------------------------------------------
1 | export class WTapps_Utils {
2 |
3 |
4 | static capitalizeFirstLetter(string) {
5 | return string.substring(0, 1).toUpperCase() + string.substring(1);
6 | }
7 |
8 |
9 | static getCookie(name) {
10 | // console.log("Welcome to WTapps_Utils - getCookie:",name);
11 | return WikiTreeAPI.cookie(name) || null;
12 | }
13 |
14 | static setCookie(name, value, options) {
15 | return WikiTreeAPI.cookie(name, value, options);
16 | }
17 |
18 |
19 | static htmlEntities(str) {
20 | return String(str)
21 | .replace(/&/g, "&")
22 | .replace(//g, ">")
24 | .replace(/"/g, """)
25 | .replace(/'/g, "'");
26 | }
27 |
28 | static isNumeric(n) {
29 | return !isNaN(parseFloat(n)) && isFinite(n);
30 | }
31 |
32 |
33 | static profileLink(wtRef, text) {
34 | return wtRef.startsWith("Private")
35 | ? text
36 | : `${text}`;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/views/fandoku/crowdyayapplause25ppllong-6948.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/fandoku/crowdyayapplause25ppllong-6948.mp3
--------------------------------------------------------------------------------
/views/fandoku/crowdyayapplause25ppllong-6948.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/fandoku/crowdyayapplause25ppllong-6948.ogg
--------------------------------------------------------------------------------
/views/fandoku/ding-idea-40142.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/fandoku/ding-idea-40142.mp3
--------------------------------------------------------------------------------
/views/fandoku/ding-idea-40142.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/fandoku/ding-idea-40142.ogg
--------------------------------------------------------------------------------
/views/fandoku/ok.dat:
--------------------------------------------------------------------------------
1 | YES
--------------------------------------------------------------------------------
/views/fandoku/small-applause-6695.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/fandoku/small-applause-6695.mp3
--------------------------------------------------------------------------------
/views/fandoku/small-applause-6695.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/fandoku/small-applause-6695.ogg
--------------------------------------------------------------------------------
/views/heritage/heritage.css:
--------------------------------------------------------------------------------
1 | #heritageContainer {
2 | padding: 0.3em;
3 | }
4 |
5 | div.heritage button[disabled] {
6 | background-color: gray;
7 | }
8 |
9 | #heritage-table {
10 | border-collapse: collapse;
11 | margin: 25px auto;
12 | border-radius: 5px 5px 0 0;
13 | overflow: hidden;
14 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
15 | }
16 |
17 | #heritage-table thead tr {
18 | background-color: #25422d;
19 | color: white;
20 | text-align: left;
21 | font-weight: bold;
22 | }
23 |
24 | #heritage-table th,
25 | #heritage-table td {
26 | padding: 10px 12px;
27 | }
28 |
29 | #heritage-table tbody {
30 | background-color: white;
31 | }
32 |
33 | #heritage-table tbody tr:nth-of-type(4n + 1) {
34 | background-color: #f7f6f0;
35 | }
36 | #heritage-table tbody tr:nth-of-type(4n + 2) {
37 | background-color: #f7f6f0;
38 | }
39 |
40 | #heritage-table tbody tr:last-of-type {
41 | border-bottom: 2px solid #25422d;
42 | }
43 |
44 | #heritage-Table #tree {
45 | display: block;
46 | height: 5em;
47 | border-radius: 50%;
48 | border: 3px solid forestgreen;
49 | width: 5em;
50 | margin: auto;
51 | }
52 |
53 | #heritageContainer #results-container {
54 | margin: 25px auto;
55 | text-align: center;
56 | display: flex;
57 | align-items: center;
58 | justify-content: center;
59 | }
60 |
61 | #heritageContainer #legend {
62 | border: black solid 1px;
63 | padding: 10px;
64 | margin: 10px;
65 | text-align: left;
66 | }
67 | #heritageContainer #legend .legend-bullet {
68 | display: inline-block;
69 | text-align: left;
70 | height: 16px;
71 | width: 16px;
72 | border: 1px solid;
73 | }
74 | #heritageContainer #legend .legend-label {
75 | vertical-align: text-bottom;
76 | }
77 |
78 | div.heritage select {
79 | padding: 0.3em;
80 | width: auto;
81 | background-size: 1em 2em;
82 | margin: 5px 5px 0px 0px;
83 | }
84 |
85 | #heritageContainer #generation {
86 | width: 3em;
87 | min-width: 3em;
88 | margin-right: 10px;
89 | margin-top: 0;
90 | }
91 |
92 | .heritage fieldset {
93 | display: block;
94 | margin-inline-start: 2px;
95 | margin-inline-end: 2px;
96 | padding-block-start: 0.35em;
97 | padding-inline-start: 0.75em;
98 | padding-inline-end: 0.75em;
99 | padding-block-end: 0.625em;
100 | min-inline-size: min-content;
101 | border-width: 2px;
102 | border-style: groove;
103 | border-color: rgb(192, 192, 192);
104 | border-image: initial;
105 | margin-bottom: 10px;
106 | }
107 | .heritage fieldset input {
108 | margin: 0;
109 | }
110 | .heritage fieldset label {
111 | margin-right: 0;
112 | }
113 | .heritage fieldset label.left {
114 | margin-left: 10px;
115 | }
116 | .heritage fieldset label.right {
117 | margin-right: 10px;
118 | }
119 |
120 | .heritage #help-button {
121 | background: darkgreen;
122 | color: #fff;
123 | font-size: 0.8em;
124 | padding: 0.1em 0.5em;
125 | border-radius: 50%;
126 | float: right;
127 | font-weight: bold;
128 | cursor: pointer;
129 | z-index: 11000;
130 | }
131 |
132 | .heritage #help-text {
133 | display: none;
134 | border: 3px solid forestgreen;
135 | border-radius: 1em;
136 | padding: 0.3em 1em;
137 | margin: 1em auto;
138 | position: absolute;
139 | top: 20px;
140 | right: 100px;
141 | width: 50em;
142 | background: white;
143 | box-shadow: 1em 1em 1em grey;
144 | z-index: 11000;
145 | cursor: default;
146 | }
147 |
148 | .heritage xx {
149 | position: absolute;
150 | top: 0.2em;
151 | right: 0.6em;
152 | font-size: 1em;
153 | cursor: pointer;
154 | font-weight: bold;
155 | color: red;
156 | }
157 |
158 | .heritage #help-text:hover {
159 | cursor: grab;
160 | }
161 | .heritage #help-text:active {
162 | cursor: grabbing;
163 | }
164 |
165 | #heritageContainer #g2g {
166 | text-align: center;
167 | background: lightgoldenrodyellow;
168 | }
169 |
170 | @media print {
171 | .heritage-not-printable {
172 | display: none;
173 | }
174 | }
175 |
176 | /* input#overview[type="radio"]:checked ~ #details_optons {
177 | display: block;
178 | } */
179 |
180 | #heritageContainer #details_options {
181 | display: none;
182 | }
183 |
184 | #heritageContainer #ancestors_options {
185 | display: none;
186 | }
187 |
188 | #heritageContainer .center {
189 | text-align: center;
190 | }
191 |
--------------------------------------------------------------------------------
/views/oneNameTrees/images/Native_American.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/oneNameTrees/images/Native_American.png
--------------------------------------------------------------------------------
/views/oneNameTrees/images/USBH.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/oneNameTrees/images/USBH.png
--------------------------------------------------------------------------------
/views/oneNameTrees/images/adopted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/oneNameTrees/images/adopted.png
--------------------------------------------------------------------------------
/views/oneNameTrees/images/tree.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/oneNameTrees/images/tree.gif
--------------------------------------------------------------------------------
/views/oneNameTrees/test.js:
--------------------------------------------------------------------------------
1 | function parseCenturies(input) {
2 | let centuries = [];
3 |
4 | // Splitting the input on commas and spaces for processing
5 | input = input
6 | .toLowerCase()
7 | .replace(/ to /g, "-")
8 | .replace(/[\u2013\u2014]/g, "-")
9 | .trim();
10 | let parts = input.split(/[\s,]+/);
11 |
12 | parts.forEach((part) => {
13 | if (part.includes("-")) {
14 | // Handling ranges
15 | let [start, end] = part.split("-").map((p) => p.trim());
16 | let startCentury, endCentury;
17 |
18 | // Adjust for start of range
19 | startCentury = start.match(/\d{4}s?$/) ? Math.ceil(parseInt(start) / 100) : parseInt(start);
20 | if (start.match(/\d{4}s$/)) startCentury++; // Increment for decades '1500s'
21 |
22 | // Adjust for end of range
23 | endCentury = end.match(/\d{4}s?$/) ? Math.ceil(parseInt(end) / 100) : parseInt(end);
24 | if (end.match(/\d{4}s$/)) endCentury++; // Increment for decades '2000s'
25 |
26 | // For cases like '1500-1800' where start is a year without 's'
27 | if (start.match(/\d{4}$/) && !start.endsWith("s")) startCentury++;
28 |
29 | // Populate range
30 | for (let i = startCentury; i <= endCentury; i++) {
31 | centuries.push(i);
32 | }
33 | } else {
34 | // Handling individual years, centuries, and decades
35 | let century = part.match(/\d{4}s?$/) ? Math.ceil(parseInt(part) / 100) : parseInt(part);
36 | if (part.match(/\d{4}s$/)) century++; // Increment for decades '1500s'
37 | centuries.push(century);
38 | }
39 | });
40 |
41 | // Adjust for specific years like '1500' directly
42 | centuries = centuries.map((c) => {
43 | if (input.match(/\d{4}(?!s)/) && !input.includes("-")) {
44 | return c + 1;
45 | }
46 | return c;
47 | });
48 |
49 | // Deduplicate and sort
50 | centuries = Array.from(new Set(centuries)).sort((a, b) => a - b);
51 |
52 | return centuries;
53 | }
54 |
55 | // Example usage
56 | console.log("15", parseCenturies("15")); // [15]
57 | console.log("15,16", parseCenturies("15,16")); // [15,16]
58 | console.log("15 16", parseCenturies("15 16")); // [15,16]
59 | console.log("15th, 16th", parseCenturies("15th, 16th")); // [15,16]
60 | console.log("1500", parseCenturies("1500")); // [16]
61 | console.log("1500s, 1600s", parseCenturies("1500s, 1600s")); // [15,16]
62 | console.log("1500-1800", parseCenturies("1500-1800")); // [16,17,18]
63 | console.log("1500s-2000s", parseCenturies("1500s-2000s")); // [16,17,18,19,20,21]
64 | console.log("15th-17th", parseCenturies("15th-17th")); // [15,16,17]
65 | console.log("15th to 18th", parseCenturies("15th to 18th")); // [15,16,17,18]
66 |
--------------------------------------------------------------------------------
/views/printerFriendly/PrinterFriendlyView.js:
--------------------------------------------------------------------------------
1 | window.PrinterFriendlyView = class PrinterFriendlyView extends View {
2 | static APP_ID = "PrinterFriendly";
3 | WT_DOMAIN = "https://www.wikitree.com";
4 | PERSON_FIELDS = [
5 | ...["Id", "Name", "Father", "Mother", "Gender", "Photo"],
6 | ...["IsLiving", "DataStatus", "BirthDate", "BirthDateDecade", "DeathDate", "DeathDateDecade"],
7 | ...["Prefix", "FirstName", "RealName", "MiddleName", "Nicknames", "LastNameAtBirth", "LastNameCurrent"],
8 | ...["BirthLocation", "DeathLocation"],
9 | ];
10 |
11 | constructor(wtAPI, generationsCount) {
12 | super();
13 |
14 | this.wtAPI = wtAPI;
15 | this.generationsCount = generationsCount > 0 ? generationsCount : 1;
16 | }
17 |
18 | meta() {
19 | return {
20 | title: "Printer Friendly",
21 | description: "",
22 | docs: "",
23 | };
24 | }
25 |
26 | init(containerSelector, personID) {
27 | this.template = Array(this.generationsCount).fill([]);
28 | this.loadView(containerSelector, personID);
29 | }
30 |
31 | async loadView(containerSelector, personID) {
32 | let data = await this.wtAPI.getAncestors(
33 | PrinterFriendlyView.APP_ID,
34 | personID,
35 | this.generationsCount - 1,
36 | this.PERSON_FIELDS
37 | );
38 | this.people = Object.assign({}, ...data.map((x) => ({ [x.Id.toString()]: x })));
39 |
40 | this.prepareTemplate(this.generationsCount, "O"); // O = origin
41 |
42 | // DNA here is combination of O, F, M letters, //e.g. OF - father, OFM - father's side grandma
43 | this.assignDNAToPeople(personID, "O");
44 |
45 | this.unknownDNA = Array.from(new Set(this.template.flat())).filter(
46 | (dna) =>
47 | !Object.values(this.people)
48 | .map((person) => person.dna)
49 | .includes(dna)
50 | );
51 |
52 | this.render(containerSelector);
53 | }
54 |
55 | prepareTemplate(remainingGens, dna) {
56 | const currGen = this.generationsCount - remainingGens;
57 |
58 | if (this.generationsCount === currGen) return;
59 |
60 | this.template[currGen] = this.template[currGen].concat(Array(2 ** (remainingGens - 1)).fill(`${dna}`));
61 |
62 | if (remainingGens === 0) return;
63 |
64 | this.prepareTemplate(remainingGens - 1, `${dna}F`); // F = father
65 | this.prepareTemplate(remainingGens - 1, `${dna}M`); // M = mother
66 | }
67 |
68 | assignDNAToPeople(personID, dna) {
69 | if (!this.people[personID]) return;
70 | this.people[personID]["dna"] = dna;
71 |
72 | if (this.people[personID].Father) this.assignDNAToPeople(this.people[personID].Father, `${dna}F`);
73 | if (this.people[personID].Mother) this.assignDNAToPeople(this.people[personID].Mother, `${dna}M`);
74 | }
75 |
76 | render(containerSelector) {
77 | let profiles = Object.values(this.people)
78 | .map((person) => this.renderPerson(person))
79 | .join("");
80 |
81 | let unknownProfiles = this.unknownDNA.map((dna) => this.renderUnknownDNA(dna)).join("");
82 |
83 | document.querySelector(containerSelector).innerHTML = `${profiles}${unknownProfiles}
`;
84 |
85 | profiles = document.querySelector(`${containerSelector} #profiles`);
86 |
87 | profiles.style.gridTemplateAreas = this.template[0]
88 | // switches rows and columns and serializes template to string to be used for css grid
89 | .map((_, colIndex) => '"' + this.template.map((row) => row[colIndex]).join(" ") + '"')
90 | .join("\n");
91 | }
92 |
93 | getDNARelationship(dna) {
94 | if (dna.length === 1) {
95 | return Object({ M: "Mother", F: "Father" })[dna];
96 | } else if (dna.length === 2) {
97 | return `Grand${this.getDNARelationship(dna.slice(1)).toLowerCase()}`;
98 | } else if (dna.length > 2) {
99 | return `G-${this.getDNARelationship(dna.slice(1))}`;
100 | }
101 | }
102 |
103 | renderUnknownDNA(dna) {
104 | return `
105 |
106 |
(${this.getDNARelationship(dna.slice(1))})
107 |
`;
108 | }
109 |
110 | renderPerson(person) {
111 | if (!person.dna || person.dna.length > this.generationsCount) return "";
112 |
113 | const photoUrl = person.PhotoData ? `${this.WT_DOMAIN}/${person.PhotoData.url}` : "";
114 | const photo = person.dna.length <= 3 ? `
` : "";
115 |
116 | let locations = "";
117 |
118 | if (person.dna.length < 5 && person?.BirthLocation) {
119 | locations = `${person.BirthLocation}
`;
120 | }
121 |
122 | const born = `${person?.IsLiving ? "Born " : ""}${wtDate(person, "BirthDate")}`;
123 | const died = person?.IsLiving ? "" : ` - ${wtDate(person, "DeathDate")}`;
124 |
125 | return `
126 |
127 | ${photo}
128 |
129 |
${wtCompleteName(person)}
130 |
${born}${died}
131 | ${locations}
132 |
133 |
`;
134 | }
135 | };
136 |
--------------------------------------------------------------------------------
/views/printerFriendly/printerFriendly.css:
--------------------------------------------------------------------------------
1 | #profiles {
2 | display: grid;
3 | grid-gap: 1px;
4 | margin: 1px;
5 | justify-content: center;
6 | min-width: 279mm; /* Letter paper format, smallest we will allow - A4 is 297mm */
7 | }
8 |
9 | #profiles * {
10 | font-size: 0.95em;
11 | }
12 |
13 | #profiles > div {
14 | outline: 1px solid #000;
15 | display: flex;
16 | align-items: stretch;
17 | padding: 0.2em 0.5em;
18 | text-align: center;
19 | justify-items: stretch;
20 | }
21 |
22 | .unknown-relative {
23 | flex-direction: column;
24 | justify-content: center;
25 | }
26 |
27 | .unknown-relative > p {
28 | font-size: 0.8em;
29 | opacity: 0.5;
30 | margin: 0;
31 | }
32 |
33 | .known-relative {
34 | flex-direction: column;
35 | justify-content: center;
36 | }
37 |
38 | .known-relative > div > * {
39 | margin: 0.1em 0;
40 | }
41 |
42 | .known-relative > .photo {
43 | max-height: 100px;
44 | object-fit: contain;
45 | margin: 0.2em 0;
46 | }
47 |
48 | .known-relative h2 {
49 | font-size: 1em;
50 | line-height: 1em;
51 | }
52 |
53 | @media print {
54 | #profiles {
55 | min-width: initial;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/views/slippyTree/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/copy.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/family.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/mouse.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/nonfullscreen.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/person.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/views/slippyTree/resources/trackpad.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/views/stats/sortable-theme-slick.css:
--------------------------------------------------------------------------------
1 | #statsContainer {
2 | /* line 2, ../sass/_sortable.sass */
3 | table[data-sortable] {
4 | border-collapse: collapse;
5 | border-spacing: 0;
6 | }
7 | /* line 6, ../sass/_sortable.sass */
8 | table[data-sortable] th {
9 | vertical-align: bottom;
10 | font-weight: bold;
11 | }
12 | /* line 10, ../sass/_sortable.sass */
13 | table[data-sortable] th, table[data-sortable] td {
14 | text-align: left;
15 | padding: 10px;
16 | }
17 | /* line 14, ../sass/_sortable.sass */
18 | table[data-sortable] th:not([data-sortable="false"]) {
19 | -webkit-user-select: none;
20 | -moz-user-select: none;
21 | -ms-user-select: none;
22 | -o-user-select: none;
23 | user-select: none;
24 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
25 | -webkit-touch-callout: none;
26 | cursor: pointer;
27 | }
28 | /* line 26, ../sass/_sortable.sass */
29 | table[data-sortable] th:after {
30 | content: "";
31 | visibility: hidden;
32 | display: inline-block;
33 | vertical-align: inherit;
34 | height: 0;
35 | width: 0;
36 | border-width: 5px;
37 | border-style: solid;
38 | border-color: transparent;
39 | margin-right: 1px;
40 | margin-left: 10px;
41 | float: right;
42 | }
43 | /* line 40, ../sass/_sortable.sass */
44 | table[data-sortable] th[data-sorted="true"]:after {
45 | visibility: visible;
46 | }
47 | /* line 43, ../sass/_sortable.sass */
48 | table[data-sortable] th[data-sorted-direction="descending"]:after {
49 | border-top-color: inherit;
50 | margin-top: 8px;
51 | }
52 | /* line 47, ../sass/_sortable.sass */
53 | table[data-sortable] th[data-sorted-direction="ascending"]:after {
54 | border-bottom-color: inherit;
55 | margin-top: 3px;
56 | }
57 |
58 | /* line 6, ../sass/sortable-theme-slick.sass */
59 | table[data-sortable].sortable-theme-slick {
60 | color: #333333;
61 | background: white;
62 | border: 1px solid #e0e0e0;
63 | }
64 | /* line 11, ../sass/sortable-theme-slick.sass */
65 | table[data-sortable].sortable-theme-slick thead th {
66 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #eeeeee));
67 | background-image: -webkit-linear-gradient(#ffffff, #eeeeee);
68 | background-image: -moz-linear-gradient(#ffffff, #eeeeee);
69 | background-image: -o-linear-gradient(#ffffff, #eeeeee);
70 | background-image: linear-gradient(#ffffff, #eeeeee);
71 | background-color: #f0f0f0;
72 | border-bottom: 1px solid #e0e0e0;
73 | }
74 | /* line 16, ../sass/sortable-theme-slick.sass */
75 | table[data-sortable].sortable-theme-slick tbody td {
76 | border-top: 1px solid #e0e0e0;
77 | }
78 | /* line 19, ../sass/sortable-theme-slick.sass */
79 | table[data-sortable].sortable-theme-slick tbody > tr:nth-child(odd) > td {
80 | background-color: #f9f9f9;
81 | }
82 | /* line 22, ../sass/sortable-theme-slick.sass */
83 | table[data-sortable].sortable-theme-slick th[data-sorted="true"] {
84 | -webkit-box-shadow: inset 1px 0 #bce8f1, inset -1px 0 #bce8f1;
85 | -moz-box-shadow: inset 1px 0 #bce8f1, inset -1px 0 #bce8f1;
86 | box-shadow: inset 1px 0 #bce8f1, inset -1px 0 #bce8f1;
87 | color: #3a87ad;
88 | background: #d9edf7;
89 | border-bottom-color: #bce8f1;
90 | }
91 | /* line 28, ../sass/sortable-theme-slick.sass */
92 | table[data-sortable].sortable-theme-slick th[data-sorted="true"]:first-child {
93 | -webkit-box-shadow: inset -1px 0 #bce8f1;
94 | -moz-box-shadow: inset -1px 0 #bce8f1;
95 | box-shadow: inset -1px 0 #bce8f1;
96 | }
97 | /* line 31, ../sass/sortable-theme-slick.sass */
98 | table[data-sortable].sortable-theme-slick th[data-sorted="true"]:last-child {
99 | -webkit-box-shadow: inset 1px 0 #bce8f1;
100 | -moz-box-shadow: inset 1px 0 #bce8f1;
101 | box-shadow: inset 1px 0 #bce8f1;
102 | }
103 | /* line 34, ../sass/sortable-theme-slick.sass */
104 | table[data-sortable].sortable-theme-slick th[data-sorted="true"][data-sorted-direction="descending"]:after {
105 | border-top-color: #3a87ad;
106 | }
107 | /* line 37, ../sass/sortable-theme-slick.sass */
108 | table[data-sortable].sortable-theme-slick th[data-sorted="true"][data-sorted-direction="ascending"]:after {
109 | border-bottom-color: #3a87ad;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/views/stats/sortable.min.js:
--------------------------------------------------------------------------------
1 | /*! sortable.js 0.8.0 */
2 | (function(){var a,b,c,d,e,f,g;a="table[data-sortable]",d=/^-?[£$¤]?[\d,.]+%?$/,g=/^\s+|\s+$/g,c=["click"],f="ontouchstart"in document.documentElement,f&&c.push("touchstart"),b=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},e={init:function(b){var c,d,f,g,h;for(null==b&&(b={}),null==b.selector&&(b.selector=a),d=document.querySelectorAll(b.selector),h=[],f=0,g=d.length;g>f;f++)c=d[f],h.push(e.initTable(c));return h},initTable:function(a){var b,c,d,f,g,h;if(1===(null!=(h=a.tHead)?h.rows.length:void 0)&&"true"!==a.getAttribute("data-sortable-initialized")){for(a.setAttribute("data-sortable-initialized","true"),d=a.querySelectorAll("th"),b=f=0,g=d.length;g>f;b=++f)c=d[b],"false"!==c.getAttribute("data-sortable")&&e.setupClickableTH(a,c,b);return a}},setupClickableTH:function(a,d,f){var g,h,i,j,k,l;for(i=e.getColumnType(a,f),h=function(b){var c,g,h,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D;if(b.handled===!0)return!1;for(b.handled=!0,m="true"===this.getAttribute("data-sorted"),n=this.getAttribute("data-sorted-direction"),h=m?"ascending"===n?"descending":"ascending":i.defaultSortDirection,p=this.parentNode.querySelectorAll("th"),s=0,w=p.length;w>s;s++)d=p[s],d.setAttribute("data-sorted","false"),d.removeAttribute("data-sorted-direction");if(this.setAttribute("data-sorted","true"),this.setAttribute("data-sorted-direction",h),o=a.tBodies[0],l=[],m){for(D=o.rows,v=0,z=D.length;z>v;v++)g=D[v],l.push(g);for(l.reverse(),B=0,A=l.length;A>B;B++)k=l[B],o.appendChild(k)}else{for(r=null!=i.compare?i.compare:function(a,b){return b-a},c=function(a,b){return a[0]===b[0]?a[2]-b[2]:i.reverse?r(b[0],a[0]):r(a[0],b[0])},C=o.rows,j=t=0,x=C.length;x>t;j=++t)k=C[j],q=e.getNodeValue(k.cells[f]),null!=i.comparator&&(q=i.comparator(q)),l.push([q,k,j]);for(l.sort(c),u=0,y=l.length;y>u;u++)k=l[u],o.appendChild(k[1])}return"function"==typeof window.CustomEvent&&"function"==typeof a.dispatchEvent?a.dispatchEvent(new CustomEvent("Sortable.sorted",{bubbles:!0})):void 0},l=[],j=0,k=c.length;k>j;j++)g=c[j],l.push(b(d,g,h));return l},getColumnType:function(a,b){var c,d,f,g,h,i,j,k,l,m,n;if(d=null!=(l=a.querySelectorAll("th")[b])?l.getAttribute("data-sortable-type"):void 0,null!=d)return e.typesObject[d];for(m=a.tBodies[0].rows,h=0,j=m.length;j>h;h++)for(c=m[h],f=e.getNodeValue(c.cells[b]),n=e.types,i=0,k=n.length;k>i;i++)if(g=n[i],g.match(f))return g;return e.typesObject.alpha},getNodeValue:function(a){var b;return a?(b=a.getAttribute("data-value"),null!==b?b:"undefined"!=typeof a.innerText?a.innerText.replace(g,""):a.textContent.replace(g,"")):""},setupTypes:function(a){var b,c,d,f;for(e.types=a,e.typesObject={},f=[],c=0,d=a.length;d>c;c++)b=a[c],f.push(e.typesObject[b.name]=b);return f}},e.setupTypes([{name:"numeric",defaultSortDirection:"descending",match:function(a){return a.match(d)},comparator:function(a){return parseFloat(a.replace(/[^0-9.-]/g,""),10)||0}},{name:"date",defaultSortDirection:"ascending",reverse:!0,match:function(a){return!isNaN(Date.parse(a))},comparator:function(a){return Date.parse(a)||0}},{name:"alpha",defaultSortDirection:"ascending",match:function(){return!0},compare:function(a,b){return a.localeCompare(b)}}]),setTimeout(e.init,0),"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof exports?module.exports=e:window.Sortable=e}).call(this);
--------------------------------------------------------------------------------
/views/stats/stats.css:
--------------------------------------------------------------------------------
1 | #statsContainer {
2 | padding: 0.3em;
3 | }
4 |
5 | #stats-table {
6 | border-collapse: collapse;
7 | margin: 25px auto;
8 | border-radius: 5px 5px 0 0;
9 | overflow: hidden;
10 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
11 | }
12 |
13 | #stats-table caption {
14 | font-family: sans-serif;
15 | font-size: 1.2em;
16 | font-weight: bold;
17 | text-align: left;
18 | padding: 0.5em;
19 | background: white;
20 | border-radius: 5px 5px 0 0;
21 | border-top: 1px solid forestgreen;
22 | border-left: 1px solid forestgreen;
23 | border-right: 1px solid forestgreen;
24 | caption-side: top;
25 | }
26 |
27 | #stats-table thead tr {
28 | background-color: #25422d;
29 | color: white;
30 | text-align: left;
31 | font-weight: bold;
32 | }
33 |
34 | #stats-table th,
35 | #stats-table td {
36 | padding: 10px 12px;
37 | }
38 |
39 | #stats-table tbody {
40 | background-color: white;
41 | }
42 |
43 | #stats-table tbody tr:nth-of-type(even) {
44 | background-color: #f7f6f0;
45 | }
46 |
47 | #stats-table tbody tr:last-of-type {
48 | border-bottom: 2px solid #25422d;
49 | }
50 |
51 | #stats-table #tree {
52 | display: block;
53 | height: 5em;
54 | border-radius: 50%;
55 | border: 3px solid forestgreen;
56 | width: 5em;
57 | margin: auto;
58 | }
59 |
60 | #statsContainer #results-container {
61 | margin: 25px auto;
62 | text-align: center;
63 | }
64 |
65 | #statsContainer #results-container div {
66 | margin: 10px auto;
67 | }
68 |
69 | div.stats select {
70 | padding: 0.3em;
71 | width: auto;
72 | background-size: 1em 2em;
73 | margin: 5px 5px 0px 0px;
74 | }
75 |
76 | .stats #generations {
77 | width: 3em;
78 | min-width: 3em;
79 | margin-right: 10px;
80 | margin-top: 0;
81 | }
82 |
83 | .stats fieldset {
84 | display: block;
85 | margin-inline-start: 2px;
86 | margin-inline-end: 2px;
87 | padding-block-start: 0.35em;
88 | padding-inline-start: 0.75em;
89 | padding-inline-end: 0.75em;
90 | padding-block-end: 0.625em;
91 | min-inline-size: min-content;
92 | border-width: 2px;
93 | border-style: groove;
94 | border-color: rgb(192, 192, 192);
95 | border-image: initial;
96 | margin-bottom: 10px;
97 | }
98 | .stats fieldset input {
99 | margin: 0;
100 | }
101 | .stats fieldset label {
102 | margin-right: 0;
103 | }
104 | .stats fieldset label.left {
105 | margin-left: 10px;
106 | }
107 | .stats fieldset label.right {
108 | margin-right: 10px;
109 | }
110 |
111 | #gender {
112 | /* width: auto; */
113 | min-width: auto;
114 | margin-right: 10px;
115 | margin-top: 0;
116 | padding-right: 1.5em;
117 | }
118 |
119 | .stats #help-button {
120 | background: darkgreen;
121 | color: #fff;
122 | font-size: 0.8em;
123 | padding: 0.1em 0.5em;
124 | border-radius: 50%;
125 | float: right;
126 | font-weight: bold;
127 | cursor: pointer;
128 | z-index: 11000;
129 | }
130 |
131 | .stats #help-text {
132 | display: none;
133 | border: 3px solid forestgreen;
134 | border-radius: 1em;
135 | padding: 0.3em 1em;
136 | margin: 1em auto;
137 | position: absolute;
138 | top: 20px;
139 | right: 100px;
140 | width: 50em;
141 | background: white;
142 | box-shadow: 1em 1em 1em grey;
143 | z-index: 11000;
144 | cursor: default;
145 | }
146 |
147 | .stats .people-group {
148 | display: none;
149 | border: 3px solid forestgreen;
150 | border-radius: 1em;
151 | padding: 0.3em 1em;
152 | margin: 1em auto;
153 | position: absolute;
154 | top: 20px;
155 | /* right: 100px; */
156 | width: auto;
157 | background: white;
158 | box-shadow: 1em 1em 1em grey;
159 | z-index: 11000;
160 | cursor: default;
161 | }
162 |
163 | .stats xx {
164 | position: absolute;
165 | top: 0.2em;
166 | right: 0.6em;
167 | font-size: 1em;
168 | cursor: pointer;
169 | font-weight: bold;
170 | color: red;
171 | }
172 |
173 | .stats .people-group:hover,
174 | .stats #help-text:hover {
175 | cursor: grab;
176 | }
177 | .stats .people-group:active,
178 | .stats #help-text:active {
179 | cursor: grabbing;
180 | }
181 | .peopleGroupTable td {
182 | padding: 0.2em;
183 | }
184 | .peopleGroupTable caption {
185 | font-family: sans-serif;
186 | font-size: 1.2em;
187 | font-weight: bold;
188 | /* text-align: left; */
189 | padding: 0.5em;
190 | /* background: white;
191 | border-top: 1px solid forestgreen;
192 | border-bottom: 1px solid forestgreen; */
193 | }
194 | .peopleGroupTable th {
195 | cursor: pointer;
196 | }
197 | .stats .pgDate {
198 | white-space: nowrap;
199 | }
200 | .stats .pgNum {
201 | text-align: center;
202 | }
203 | .stats .pgAge {
204 | text-align: right;
205 | }
206 |
207 | #g2g {
208 | text-align: center;
209 | background: lightgoldenrodyellow;
210 | }
211 |
212 | @media print {
213 | .stats-not-printable {
214 | display: none;
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/views/surnames/surnames.css:
--------------------------------------------------------------------------------
1 | #surnamesList {
2 | background-color: #fff;
3 | padding: 20px;
4 | }
5 |
6 | .generationRow {
7 | text-align: center;
8 | border-bottom: 1px dotted black;
9 | display: flex;
10 | justify-content: center;
11 | }
12 | .paternalColumn {
13 | background-color: #eeeeff;
14 | width: 49%;
15 | margin-right: 20px;
16 | }
17 | .maternalColumn {
18 | background-color: #ffeeee;
19 | width: 49%;
20 | margin-left: 20px;
21 | }
22 | .surnameItem {
23 | display: inline-block;
24 | width: auto;
25 | margin: 0px 10px 10px 0px;
26 | }
27 | .newSurname {
28 | font-weight: bold;
29 | }
30 | #surnamesList .gen0 {
31 | font-size: 70px;
32 | }
33 | #surnamesList .gen1 {
34 | font-size: 60px;
35 | }
36 | #surnamesList .gen2 {
37 | font-size: 50px;
38 | }
39 | #surnamesList .gen3 {
40 | font-size: 40px;
41 | }
42 | #surnamesList .gen4 {
43 | font-size: 30px;
44 | }
45 | #surnamesList .gen5 {
46 | font-size: 20px;
47 | }
48 | #surnamesList .gen6 {
49 | font-size: 16px;
50 | }
51 | #surnamesList .genx {
52 | font-size: 14px;
53 | }
54 |
--------------------------------------------------------------------------------
/views/webs/websSingleMRCA-T.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/webs/websSingleMRCA-T.png
--------------------------------------------------------------------------------
/views/webs/websSingleMRCA-V.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikitree/wikitree-dynamic-tree/dd251ce8e7f2739e7df17f3a051d0f8b7cb40e02/views/webs/websSingleMRCA-V.png
--------------------------------------------------------------------------------
/views/wtPlusMaps/wtPlusMaps.js:
--------------------------------------------------------------------------------
1 | /*
2 | * wtPlus Maps
3 | *
4 | */
5 |
6 | window.WtPlusMaps = class WtPlusMaps extends View {
7 | static APP_ID = "wtPlusMaps";
8 | meta() {
9 | return {
10 | // short title - will be in select control
11 | title: "WT+ Maps",
12 | // some longer description or usage
13 | description: "Displays the map of ancestors as they moved arround the world.",
14 | // link pointing at some webpage with documentation
15 | docs: "https://www.wikitree.com/wiki/Help:WikiTree_Plus#Map_navigator_on_WikiTree.2B",
16 | };
17 | }
18 |
19 | aPerson_id;
20 |
21 | setMap(mapType) {
22 | document.getElementById("map").src = `https://plus.wikitree.com/findmap.htm?aid=${this.aPerson_id}&grouptype=${mapType}&appid=TA-wtPlusMaps-but${mapType}`;
23 | }
24 |
25 | init(container_selector, person_id) {
26 | this.aPerson_id = person_id;
27 | document.querySelector(container_selector).innerHTML = `
28 |
29 |
30 |
31 |
32 | `;
33 | }
34 | };
35 |
36 |
--------------------------------------------------------------------------------