├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── Daily Note.md ├── EXAMPLE.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── dailyNote.css ├── esbuild.config.mjs ├── getCurrentWeather.ts ├── getForecastWeather.ts ├── images ├── Demo.gif ├── Format1-1.png ├── Format2-1.png ├── Format3-1.png ├── Format3-2.png ├── Format4-1.png ├── Format4-2.png └── Statusbar-1.png ├── main.js ├── main.ts ├── manifest.json ├── package-lock.json ├── package.json ├── styles.css ├── tsconfig.json ├── version-bump.mjs └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | tab_width = 2 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | # Force batch scripts to always use CRLF line endings so that if a repo is accessed 5 | # in Windows via a file share from Linux, the scripts will work. 6 | *.{cmd,[cC][mM][dD]} text eol=crlf 7 | *.{bat,[bB][aA][tT]} text eol=crlf 8 | *.{ics,[iI][cC][sS]} text eol=crlf 9 | 10 | # Image files are treated as binary by default. 11 | *.jpg binary 12 | *.png binary 13 | *.gif binary 14 | 15 | # Force bash scripts to always use LF line endings so that if a repo is accessed 16 | # in Unix via a file share from Windows, the scripts will work. 17 | *.sh text eol=lf 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | # main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | 24 | # Project notes 25 | .pnotes 26 | 27 | # Project time tracker 28 | .vscode/time_log.json 29 | 30 | # Hotreload - For plugin Development 31 | .hotreload 32 | StartMonitoring.ps1 33 | monitor-log.txt 34 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | ## [1.8.0] 2024-05-26 5 | ### Added 6 | - 5 day forecast 7 | - 33 new placeholders (Times 40 for each time slot - 1320 total) 8 | - 3 complex placeholders `%next12%`, `%next24%`, and `%next48%` 9 | - See the README.md files `Forecast Weather Placeholders` section for details 10 | ### Updated 11 | - README.md 12 | - Added the list of available placeholders 13 | - Updated settings descriptions 14 | 15 | ## [1.7.1] 2024-02-24 16 | ### Fixed 17 | - String settings names were named incorrectly (This effectively reset your defined strings to defaults) 18 | - The original edited strings are still in the data file, this fix just points back to them 19 | - This only affects anyone who had changed the default strings 20 | 21 | ## [1.7.0] 2024-02-18 22 | ### Added 23 | - Settings for Latitude and Longitude 24 | - Please note that API requests by city name have been deprecated although they are still available for use 25 | - Note that setting these will override the location setting 26 | - Use these if you are having issues with getting data returned for the wrong city 27 | - Leave them blank to use the location setting (Not recommended) 28 | - `Geocoding API` - This provides you with the requested cities name, state, country, latitude and longitude 29 | - Settings button for using the `Geocoding API` to retrieve cities name, state, country, latitude and longitude 30 | - This will prompt you with a list of up to 5 locations based on your city name search query 31 | - The settings for location name, latitude and longitude will automatically be updated to the data from your selection 32 | - This should solve any issues with receiving data for the wrong city 33 | - The weather string placeholders `%latitude%` and `%longitude%` 34 | - These values are returned from the API call and are not from the new settings for Latitude and Longitude 35 | - `Air Pollution API` - This provides you with the current Air Quality Index with the corresponding placeholders 36 | - `%aqinumber%` - Numbers 1 to 5 which match with the strings below 37 | - `%aqistring%` - The strings that match the numbers 1 to 5 - 'Good', 'Fair', 'Moderate', 'Poor', 'Very Poor' 38 | - Github repository links in the settings UI 39 | ### Changed 40 | - Wind speed in meters/second now rounded to nearest whole number 41 | ### Fixed 42 | - Now returns an error message on API error Eg. `Error Code 404: city not found` will be displayed as the weather string 43 | ### Updated 44 | - Settings UI improved the layout and descriptions 45 | 46 | ## [1.6.1] 2024-01-13 47 | ### Added 48 | - The files `Daily Note.md` and `dailyNote.css` used in `EXAMPLE.md` file 49 | ### Updated 50 | - The documentation in the `EXAMPLE.md` document has been updated 51 | 52 | ## [1.6.0] 2023-11-07 53 | ### Added 54 | - New macro for wind speed in meters per second %wind-speed-ms% 55 | ### Fixed 56 | - "Failed to fetch weather data TypeError: Cannot read properties of undefined (reading '0')" while entering location in settings 57 | 58 | ## [1.5.1] 2023-04-11 59 | ### Fixed 60 | - Selected exclude folder now excludes files in subfolders as well 61 | - Corrected some typos 62 | 63 | ## [1.5.0] 2023-03-18 64 | ### Added 65 | - Weather Description Emojis 66 | - Macro `%desc-em%` will be replaced with an emoji of the current weather description 67 | 68 | ## [1.4.0] 2023-03-18 69 | ### Added 70 | - Language support for the following languages 71 | - af - Afrikaans 72 | - al - Albanian 73 | - ar - Arabic 74 | - az - Azerbaijani 75 | - bg - Bulgarian 76 | - ca - Catalan 77 | - cz - Czech 78 | - da - Danish 79 | - de - German 80 | - el - Greek 81 | - en - English 82 | - eu - Basque 83 | - fa - Persian (Farsi) 84 | - fi - Finnish 85 | - fr - French 86 | - gl - Galician 87 | - he - Hebrew 88 | - hi - Hindi 89 | - hr - Croatian 90 | - hu - Hungarian 91 | - id - Indonesian 92 | - it - Italian 93 | - ja - Japanese 94 | - kr - Korean 95 | - la - Latvian 96 | - lt - Lithuanian 97 | - mk - Macedonian 98 | - no - Norwegian 99 | - nl - Dutch 100 | - pl - Polish 101 | - pt - Portuguese 102 | - pt_br - Português Brasil 103 | - ro - Romanian 104 | - ru - Russian 105 | - sv - Swedish 106 | - sk - Slovak 107 | - sl - Slovenian 108 | - sp - Spanish 109 | - sr - Serbian 110 | - th - Thai 111 | - tr - Turkish 112 | - ua - uk Ukrainian 113 | - vi - Vietnamese 114 | - zh_cn - Chinese Simplified 115 | - zh_tw - Chinese Traditional 116 | - zu - Zulu 117 | 118 | ## [1.3.0] 2023-03-10 119 | ### Fixed 120 | - removed obsidian from ID 121 | 122 | ## [1.2.0] 2023-03-02 123 | ### Added 124 | - Cloud coverage %clouds% (Percentage) 125 | - Rain past 1 hour %rain1h% (in millimeters) 126 | - Rain past 3 hours %rain3h% (in millimeters) 127 | - Snow past 1 hour %snow1h% (in millimeters) 128 | - Snow past 3 hours %snow3h% (in millimeters) 129 | - Precipitation past 1 hour %precipitation1h% (in millimeters - Rain or Snow) 130 | - Precipitation past 3 hours %precipitation3h% (in millimeters - Rain or Snow) 131 | 132 | ## [1.1.0] 2023-02-07 133 | ### Added 134 | - Now runs on mobile (Display weather in statusbar is disabled) 135 | - New setting `Exclude Folder` which will exclude a folder from automatic template strings replacement (set this to your templates folder so it does not replace the strings in your template) 136 | 137 | ### Fixed 138 | - Dynamic weather in DIV's should be shown more reliably 139 | 140 | ## [1.0.0] 2022-12-22 141 | - Initial release 142 | -------------------------------------------------------------------------------- /Daily Note.md: -------------------------------------------------------------------------------- 1 | --- 2 | cssclasses: daily, calendar 3 | banner: "![[daily-note-banner.jpg]]" 4 | banner_x: 0.5 5 | banner_y: 0.3 6 | obsidianUIMode: preview 7 | title: <%tp.file.title%> 8 | created: <%tp.date.now()%> 9 | modified: <%tp.date.now()%> 10 | last_reviewed: <%tp.date.now()%> 11 | author: "William McKeever" 12 | source: 13 | status: 14 | webclip: false 15 | tags: [daily_note] 16 | aliases: [] 17 | description: "Daily Note" 18 | --- 19 |
%weather3%
20 |
21 | 22 | | <%moment(tp.file.title, 'YYYY-MM-DD').subtract(3, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').subtract(2, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').subtract(1, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').add(1, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').add(2, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').add(3, 'd').format('ddd')%> | 23 | |:------:|:------:|:------:|:------:|:------:|:------:|:----------:| 24 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(17, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(17, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(16, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(16, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(15, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(15, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(14, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(14, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(13, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(13, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(12, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(12, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(11, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(11, 'd').format('DD')%>]] | 25 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(10, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(10, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(9, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(9, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(8, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(8, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(7, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(7, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(6, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(6, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(5, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(5, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(4, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(4, 'd').format('DD')%>]] | 26 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(3, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(3, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(2, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(2, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(1, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(1, 'd').format('DD')%>]] | ==**[[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').format('DD')%>]]**== | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(1, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(1, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(2, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(2, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(3, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(3, 'd').format('DD')%>]] | 27 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(4, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(4, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(5, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(5, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(6, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(6, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(7, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(7, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(8, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(8, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(9, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(9, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(10, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(10, 'd').format('DD')%>]] | 28 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(11, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(11, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(12, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(12, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(13, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(13, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(14, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(14, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(15, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(15, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(16, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(16, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(17, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(17, 'd').format('DD')%>]] | 29 | 30 | > ###### [[<% tp.date.now("YYYY-MM-DD", -1, tp.file.title, "YYYY-MM-DD") %>|⬅]] <% tp.file.title %> [[<% tp.date.now("YYYY-MM-DD", 1, tp.file.title, "YYYY-MM-DD") %>|➡]] 31 | > ##### 🔹 <% tp.date.now("dddd 🔹 MMMM Do 🔹 YYYY", 0, tp.file.title, "YYYY-MM-DD") %> 🔹 32 | > 33 | <%tp.web.daily_quote()%> 34 | 35 | --- 36 | 37 | ## Todo Today 38 | - [ ] `button-review` Review recent notes. 39 | 40 | ## Achievments for Today 41 | 42 | ## Research 43 | 44 | ## Ideas 45 | 46 | ## Quick notes 47 | 48 | -------------------------------------------------------------------------------- /EXAMPLE.md: -------------------------------------------------------------------------------- 1 | # My Daily Note Example 2 | 3 | ## Demonstration Information 4 | 5 | This is an example of how I add the weather information into my Daily Notes. The recorded temperature is the permanently recorded temperature available at the time of my Daily Notes creation. When I hover over that it shows me the current temperature information. This is achieved through the use of CSS. All the code involved is also listed in this document and copies have been added to the repository. 6 | 7 | Please note the web site `https://api.quotable.io/` which is used by the Templater plugin was down at the time I made the example gif so please just ignore the error message. Also the lines for the calendar are very long and may not be displayed properly in your markdown viewer or on Github. 8 | 9 | It makes use of the following plugins, [banners](https://github.com/noatpad/obsidian-banners), [templater](https://github.com/SilentVoid13/Templater), [Buttons](https://github.com/shabegom/buttons) and of course this plugin [OpenWeather](https://github.com/willasm/obsidian-open-weather). 10 | 11 | ### Screenshot 12 | 13 | ![Demonstration](images/Demo.gif) 14 | 15 | ## [My Daily Note Template](Daily%20Note.md) 16 | 17 | ```markdown 18 | --- 19 | cssclasses: daily, calendar 20 | banner: "![[daily-note-banner.jpg]]" 21 | banner_x: 0.5 22 | banner_y: 0.3 23 | obsidianUIMode: preview 24 | title: <%tp.file.title%> 25 | created: <%tp.date.now()%> 26 | modified: <%tp.date.now()%> 27 | last_reviewed: <%tp.date.now()%> 28 | author: "William McKeever" 29 | source: 30 | status: 31 | webclip: false 32 | tags: [daily_note] 33 | aliases: [] 34 | description: "Daily Note" 35 | --- 36 |
%weather3%
37 |
38 | 39 | | <%moment(tp.file.title, 'YYYY-MM-DD').subtract(3, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').subtract(2, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').subtract(1, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').add(1, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').add(2, 'd').format('ddd')%> | <%moment(tp.file.title, 'YYYY-MM-DD').add(3, 'd').format('ddd')%> | 40 | |:------:|:------:|:------:|:------:|:------:|:------:|:----------:| 41 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(17, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(17, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(16, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(16, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(15, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(15, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(14, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(14, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(13, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(13, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(12, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(12, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(11, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(11, 'd').format('DD')%>]] | 42 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(10, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(10, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(9, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(9, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(8, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(8, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(7, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(7, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(6, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(6, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(5, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(5, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(4, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(4, 'd').format('DD')%>]] | 43 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(3, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(3, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(2, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(2, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').subtract(1, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').subtract(1, 'd').format('DD')%>]] | ==**[[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').format('DD')%>]]**== | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(1, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(1, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(2, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(2, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(3, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(3, 'd').format('DD')%>]] | 44 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(4, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(4, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(5, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(5, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(6, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(6, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(7, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(7, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(8, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(8, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(9, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(9, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(10, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(10, 'd').format('DD')%>]] | 45 | | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(11, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(11, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(12, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(12, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(13, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(13, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(14, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(14, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(15, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(15, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(16, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(16, 'd').format('DD')%>]] | [[Daily/<%moment(tp.file.title, 'YYYY-MM-DD').add(17, 'd').format('YYYY-MM-DD')%>\|<%moment(tp.file.title, 'YYYY-MM-DD').add(17, 'd').format('DD')%>]] | 46 | 47 | > ###### [[<% tp.date.now("YYYY-MM-DD", -1, tp.file.title, "YYYY-MM-DD") %>|⬅]] <% tp.file.title %> [[<% tp.date.now("YYYY-MM-DD", 1, tp.file.title, "YYYY-MM-DD") %>|➡]] 48 | > ##### 🔹 <% tp.date.now("dddd 🔹 MMMM Do 🔹 YYYY", 0, tp.file.title, "YYYY-MM-DD") %> 🔹 49 | 50 | <%tp.web.daily_quote()%> 51 | 52 | --- 53 | 54 | ## Todo Today 55 | - [ ] `button-review` Review recent notes. 56 | 57 | ## Achievments for Today 58 | 59 | ## Research 60 | 61 | ## Ideas 62 | 63 | ## Quick notes 64 | 65 | ``` 66 | 67 | ## [My Daily Notes CSS (dailyNote.css)](dailyNote.css) 68 | 69 | ```css 70 | /*====================*/ 71 | /* Daily Note Styling */ 72 | /*====================*/ 73 | .daily { 74 | padding-left: 25px !important; 75 | padding-right: 25px !important; 76 | padding-top: 20px !important; 77 | } 78 | 79 | /* Transition Effect Weather One */ 80 | .weather_current_1, 81 | .weather_historical_1 { 82 | transition: 0.3s; 83 | } 84 | 85 | /* Transition Effect Weather Two */ 86 | .weather_current_2, 87 | .weather_historical_2 { 88 | transition: 0.3s; 89 | } 90 | 91 | /* Transition Effect Weather Three */ 92 | .weather_current_3, 93 | .weather_historical_3 { 94 | transition: 0.3s; 95 | } 96 | 97 | /* Transition Effect Weather Four */ 98 | .weather_current_4, 99 | .weather_historical_4 { 100 | transition: 0.3s; 101 | } 102 | 103 | /* Historical & Current weather One, Two, Three and Four common settings */ 104 | .weather_historical_1, .weather_current_1, .weather_historical_2, .weather_current_2, .weather_historical_3, .weather_current_3, .weather_historical_4, .weather_current_4 { 105 | display: flex; 106 | float: left; 107 | clear: left; 108 | align-items: center; 109 | top: 45px; 110 | left: 55px; 111 | white-space: pre; 112 | position: absolute; 113 | font-family: monospace; 114 | font-size: 14pt !important; 115 | margin: 10px 5px; 116 | padding: 10px 20px; 117 | border-radius: 20px; 118 | box-shadow: 3px 3px 2px #414654; 119 | cursor: pointer; 120 | } 121 | 122 | /* Historical weather at top of the document over banner */ 123 | .weather_historical_1, .weather_historical_2, .weather_historical_3, .weather_historical_4 { 124 | color: #d0dce9; 125 | background-color: #0d3d56; 126 | } 127 | 128 | /* Current weather at top of the document over banner */ 129 | .weather_current_1, .weather_current_2, .weather_current_3, .weather_current_4 { 130 | color: #c4caa5; 131 | background-color: #133e2c; 132 | opacity: 0; 133 | } 134 | 135 | /* Show Current weather One on hover */ 136 | .weather_current_1:hover { 137 | opacity: 1; 138 | } 139 | 140 | /* Show Current weather Two on hover */ 141 | .weather_current_2:hover { 142 | opacity: 1; 143 | } 144 | 145 | /* Show Current weather Three on hover */ 146 | .weather_current_3:hover { 147 | opacity: 1; 148 | } 149 | 150 | /* Show Current weather Four on hover */ 151 | .weather_current_4:hover { 152 | opacity: 1; 153 | } 154 | 155 | /* Daily Note Name H6 Styling (YYYY-MM-DD) */ 156 | .daily h6 { 157 | font-size: 1.75em; 158 | color: #4cbf90; 159 | border-width: 1px; 160 | padding-bottom: 3px; 161 | text-align: center; 162 | } 163 | 164 | /* Daily Note Date H5 Styling (🔹 Thursday 🔹 10th November 🔹 2022 🔹) */ 165 | .daily h5 { 166 | font-size: 1.25em; 167 | color: #23a49d; 168 | border-width: 1px; 169 | padding-bottom: 3px; 170 | text-align: center; 171 | } 172 | 173 | /* Hide the frontmatter for Daily Notes 174 | .daily .frontmatter-container { 175 | display: none; 176 | } 177 | */ 178 | 179 | /*==============================*/ 180 | /* Display Daily Notes Calendar */ 181 | /*==============================*/ 182 | .calendar table { 183 | top: 30px; 184 | right: 30px; 185 | position: absolute; 186 | background-color: #0d3d56; 187 | border-radius: 10px; 188 | } 189 | 190 | .calendar thead { 191 | background-color: #147980; 192 | } 193 | 194 | .calendar th { 195 | color: goldenrod !important; 196 | } 197 | 198 | .calendar table th:first-of-type { 199 | border-top-left-radius: 6px; 200 | } 201 | .calendar table th:last-of-type { 202 | border-top-right-radius: 6px; 203 | } 204 | 205 | .calendar .internal-link { 206 | color: rgb(215, 146, 27); 207 | } 208 | 209 | .calendar .internal-link.is-unresolved { 210 | color: silver; 211 | } 212 | 213 | .calendar td strong { 214 | border-radius: 50%; 215 | padding: 3px; 216 | border: 2px solid #229ecf; 217 | color: black !important; 218 | } 219 | 220 | .calendar mark { 221 | /* color: #fd0707 !important; */ 222 | background: #0d3d56; 223 | } 224 | 225 | .calendar strong .internal-link{ 226 | color: #23bca5 !important; 227 | /* background: #0d3d56; */ 228 | } 229 | 230 | ``` 231 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2022 William McKeever 2 | 3 | Permission is hereby granted, 4 | free of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenWeather Plugin for Obsidian 2 | 3 | > IMPORTANT NOTICE - THIS PLUGIN IS NOW DISCONTINUED! 4 | > 5 | > (Continue reading though - I have good news to announce as well) 6 | > 7 | > 8 | > Open Weather has discontinued API 2.5 on which this plugin is based. They do offer API 3.0 which is free, but it requires a credit card to subscribe to the API. I'm not interested in giving some company a way to automatically charge me for something in the future when/if they decide to. I'm pretty sure that would never actually happen, but I can say that I am positively sure it can not happen if they do not have my credit card info. Also if they did start charging the users of this plugin (around 10,000 users) for some unforseen reason, that would have a lot of people very unhappy with me and that would be unpleasant to say the least... 9 | > 10 | > One other point is that there are enough differences between the two API's that it would basically require a complete re-write of the plugin which leads into my good news... I have very nearly completed a new weather plugin based on the [Visual Crossing Weather API](https://www.visualcrossing.com/weather-api). It is free to subscribe to and does not require anything other than your email address. You can [sign up here](https://www.visualcrossing.com/sign-up). It offers many great features that were not included with Open Weather. For example, 15 complete forecast days while Open Weather only had 5 and for 21 hours out of every 24, day 1 and 5 did not return the full data for those days. VCW API also returns hourly data for every one of those 15 days while OW only returns the data in 3 hour blocks. Other nice features that VCW API offers and OW API is missing is weather alerts, data is returned in local time rather than GMT (No conversions needed). The API documentation is also much better and it has an active support forum. Overall it is just a nicer API to work with. 11 | > 12 | > If you want to try the new plugin now you can find the [Visual Crossing Weather plugin here](https://github.com/willasm/vc-weather). It has some big improvements over the old plugin. It now has 5 locations you can get the weather data for as opposed to just one. I have doubled the number of weather templates from 4 to 8. There are now 2 statusbar strings which can be cycled every 30 seconds. The defaults are the first one has info on todays weather, the second displays info for tommorows weather. Note while this is still a work in progress it has almost every feature working now (Two feature that I still want to add are not yet completed). The documentation is also basically non-existent at the moment although I do have the complete list of current macros created (nearly 4000 already) and it is displayed in an easy to read table format. I have uploaded the main.js file as well if you would like to do a manual install or you could use the BRAT plugin to do the work for you. Anyone willing to Beta test it would be a huge help if they could provide any feedback. 13 | > 14 | > Please note that the API was supposed to be discontinued 3 weeks ago, but as right now it is still returnig data. This could stop at any time!!! It is fortunate that it has continued this long as it has given me the time to write the new plugin. I will request the removal of this plugin from community plugins list when the API stops working or I have added the new plugin to community plugins list. 15 | > 16 | > Thank you to all the plugins users since its creation, William McKeever 17 | > 18 | 19 | 20 | 21 | 22 | ## Features 23 | - Display current weather in the statusbar 24 | - Insert current weather into your documents 25 | - Four customizable weather strings available 26 | - Customizable statusbar weather string 27 | - [Template support](#template-support) for automatic weather insertion into your new documents 28 | - [DIV support](#div-support) for dynamic weather 29 | - 5 Day forecast available 30 | 31 | ## Default Weather Strings with Example Screenshots 32 | 33 | #### **Statusbar String** 34 | `' | %desc% | Current Temp: %temp%°C | Feels Like: %feels%°C | '` 35 | 36 | ![Statusbar](/images/Statusbar-1.png) 37 | 38 | #### **Weather String One** 39 | `'%desc% • Current Temp: %temp%°C • Feels Like: %feels%°C\n'` 40 | 41 | ![Format One](/images/Format1-1.png) 42 | 43 | #### **Weather String Two** 44 | `'%name%: %dateMonth4% %dateDay2% - %timeH2%:%timeM% %ampm1%\nCurrent Temp: %temp%°C • Feels Like: %feels%°C\nWind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^\nSunrise: %sunrise% • Sunset: %sunset%\n'` 45 | 46 | ![Format Two](/images/Format2-1.png) 47 | 48 | #### **Weather String Three** 49 | `'%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Recorded Temp: %temp% • Felt like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%'` 50 | 51 | ![Format Three](/images/Format3-1.png) 52 | 53 | #### **Weather String Four** 54 | `'%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Current Temp: %temp% • Feels like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%'` 55 | 56 | ![Format Four](/images/Format4-1.png) 57 | 58 | #### **Format Strings Three & Four within DIV's and styled wih CSS** 59 | 60 | Format String Three... 61 | 62 | ![Format Three](/images/Format3-2.png) 63 | 64 | Format String Four... 65 | 66 | ![Format Four](/images/Format4-2.png) 67 | 68 | Note: The `\n`'s are not required when editing these in the settings. Simply enter a `return` to add a new line and the `\n` will be added to the saved settings file. The `
`'s in string formats 3 & 4 are required for use in HTML. 69 | 70 | See [EXAMPLE.md](EXAMPLE.md) for a demonstration of how I use this in my Daily Template. 71 | 72 | ## Settings 73 | 74 | #### **OpenWeather API Key** 75 | Enter your OpenWeather API Key here (Required) 76 | 77 | A free OpenWeather API key is required for the plugin to work. 78 | Go to https://openweathermap.org to register and get a key. 79 | Direct link to signup page https://home.openweathermap.org/users/sign_up. 80 | 81 | Note: You will need to verify your email address, then your API key will be emailed to you. The key itself may take a couple of hours before it is activated. All this information will be included in the email they send to you. 82 | 83 | #### **Use Geocoding API to get location (recommended)** 84 | This Geocoding API returns the requested locations name, state, country, latitude and longitude allowing you to choose the correct location. This is beneficial in cases where your city has a common name shared by other cities. To use this, enter your cities name in the text field and press the `Get location` button. You will be prompted with a list of up to 5 locations to choose from. 85 | 86 | #### **Enter Location** 87 | Note: It is recommended to use the new `Use Geocoding API to get location (recommended)` command to fill this in for you. 88 | 89 | Enter your city's name (This setting is required unless latitude and longitude are defined) 90 | Note: If you are getting the wrong data try including your state and country codes. They can be entered as {city name},{state code},{country code}. Eg. South Bend, WA, US (The commas are required). If you are still having issues getting the correct data, then use the Latitude and Longitude settings instead. 91 | 92 | #### **Enter Latitude** 93 | Note: It is recommended to use the new `Use Geocoding API to get location (recommended)` command to fill this in for you. 94 | 95 | Enter your city's latitude (Setting Latitude and Longitude will override the Location setting) 96 | 97 | Please note that API requests by city name have been deprecated although they are still available for use. The preferred method is to use latitude and longitude. 98 | 99 | #### **Enter Longitude** 100 | Note: It is recommended to use the new `Use Geocoding API to get location (recommended)` command to fill this in for you. 101 | 102 | Enter your city's longitude (Setting Latitude and Longitude will override the Location setting) 103 | 104 | Please note that API requests by city name have been deprecated although they are still available for use. The preferred method is to use latitude and longitude. 105 | 106 | #### **Units of Measurement** 107 | Metric, Imperial and Standard units can be selected here. (Note: Standard is in Kelvin, not really useful in most cases) 108 | 109 | #### **Language** 110 | Supported languages available (46 languages total) 111 | Note: This only applies to text that is returned by the Open Weather API. This does not change the text in the defined weather strings. If you want the text in the default weather strings in another language you will need to edit them directly in the settings. 112 | 113 | #### **Exclude Folder** 114 | Folder to exclude from automatic [Template](#template-support) strings replacement. This should be set to your vaults template folder. The exclusion includes any subfolders within the selected folder. 115 | 116 | #### **Weather Strings ** 117 | Define your weather strings here (4 strings are available + 1 for the statusbar) 118 | 119 | _Tip: These strings can contain anything you want, not just weather information._ 120 | 121 | #### **Show Weather in Statusbar** Note: This will not be displayed on mobile app 122 | Toggle display of the current weather in the statusbar on or off 123 | 124 | #### **Weather String Format Statusbar** Note: This will not be displayed on mobile app 125 | Define your statusbar weather string here 126 | 127 | #### **Update Frequency** 128 | Time interval to update the weather displayed in the statusbar and [DIV's](#div-support) (1, 5, 10, 15, 20, 30 or 60 minutes) 129 | 130 | ## Weather String Placeholders 131 | These macros contained within the weather string will be replaced with the appropiate data. 132 | 133 | ### Current Weather Placeholders 134 | - Weather Description Text `%desc%` 135 | - Weather Description Emoji `%desc-em%` 136 | - Weather Icon `%icon%` - See note below 137 | - Weather Icon Double sized `%icon2x%` - See note below 138 | - Current Temperature `%temp%` 139 | - Feels Like `%feels%` 140 | - Temperature Min `%tempmin%` 141 | - Temperature Max `%tempmax%` 142 | - Air Pressure `%pressure%` 143 | - Humidity `%humidity%` 144 | - Pressure at Sea Level `%pressure-sl%` 145 | - Pressure at Ground Level `%pressure-gl%` 146 | - Visibility `%visibility%` 147 | - Wind Speed `%wind-speed%` - km/h for Metric, mph for Imperial 148 | - Wind Speed `%wind-speed-ms%` - m/s (Meters per second) 149 | - Wind Direction `%wind-dir%` - Eg. Northwest 150 | - Wind Gust `%wind-gust%` - See note below 151 | - Cloud coverage `%clouds%` (Percentage) 152 | - Rain past 1 hour `%rain1h%` (in millimeters) 153 | - Rain past 3 hours `%rain3h%` (in millimeters) 154 | - Snow past 1 hour `%snow1h%` (in millimeters) 155 | - Snow past 3 hours `%snow3h%` (in millimeters) 156 | - Precipitation past 1 hour `%precipitation1h%` (in millimeters - Rain or Snow) 157 | - Precipitation past 3 hours `%precipitation3h%` (in millimeters - Rain or Snow) 158 | - Sunrise `%sunrise%` - 08:30:30 (24 hour format) 159 | - Sunset `%sunset%` - 19:30:30 (24 hour format) 160 | - City Name `%name%` - Eg. Edmonton 161 | - Latitude `%latitude%` - Eg. 46.66 162 | - Longitude `%longitude%` - Eg. -123.80 163 | - Air Quality Index as number `%aqinumber%` - 1 to 5 (Order matches the strings list) 164 | - Air Quality Index as string `%aqistring%` - 'Good', 'Fair', 'Moderate', 'Poor', 'Very Poor' (Order matches the numbers list) 165 | - (Date & Time) - The date & time of the most recent data information that OpenWeather API has available 166 | - year1 `%dateYear1%` - 2022 167 | - year2 `%dateYear2%` - 22 168 | - month1 `%dateMonth1%` - 1 169 | - month2 `%dateMonth2%` - 01 170 | - month3 `%dateMonth3%` - Jan 171 | - month4 `%dateMonth4%` - January 172 | - date1 `%dateDay1%` - 2 173 | - date2 `%dateDay2%` - 02 174 | - ampm1 `%ampm1%` - "AM" 175 | - ampm2 `%ampm2%` - "am" 176 | - hour1 `%timeH1%` - 23 (24 hour) 177 | - hour2 `%timeH2%` - 1 (12 hour) 178 | - min `%timeM%` - 05 179 | - sec `%timeS%` - 05 180 | 181 | ### Forecast Weather Placeholders 182 | Note: The 5 day forecast is returned in 3 hour increments (total of 40 data objects). This means that the data returned by the API does not start at 12:00 am tommorow, but for the next 3 hour slice of available data. This means that only once a day can you get the full forecast for 5 days (just before midnight). At all other times you will recieve partial data for today and partial data for the last day. You will need to account for this when defining your weather strings. To make it easier for you I have included the placeholders `%next12%`, `%next24%`, and `%next48%` (see placeholder example) and will add more in the future. 183 | 184 | Note: The placeholders represent the 3 hour forecast objects and are numbered 00, 01, 02, ... 39 in the placeholders. 185 | 186 | - `%fyear_00%` to `%fyear_39%` - Forecast Year Eg. 2024 187 | - `%fmonth_00%` to `%fmonth_39%` - Forecast Month Eg. 05 188 | - `%fdate_00%` to `%fdate_39%` - Forecast Date Eg. 26 189 | - `%fhours_00%` to `%fhours_39%` - Forecast Hours Eg. 18 190 | - `%fmins_00%` to `%fmins_39%` - Forecast Minutes Eg. 00 (will always be 00) 191 | - `%fsecs_00%` to `%fsecs_39%` - Forecast Seconds Eg. 00 (will always be 00) 192 | - `%dt_localtime_00%` to `%dt_localtime_39%` - Forecast local date and time string Eg. 2024-05-26 18:00:00 193 | - `%d_localtime_00%` to `%d_localtime_39%` - Forecast local date string Eg. 2024-05-26 194 | - `%ds_localtime_00%` to `%ds_localtime_39%` - Forecast local date short string Eg. 05-26 195 | - `%t_localtime_00%` to `%t_localtime_39%` - Forecast local time string Eg. 18:00:00 196 | - `%ts_localtime_00%` to `%ts_localtime_39%` - Forecast local time short string Eg. 18:00 197 | - `%ftemp_00%` to `%ftemp_39%` - Forecast temperature Eg. 15 198 | - `%ffeels_00%` to `%ffeels_39%` - Forecast feels like temperature Eg. 14 199 | - `%fclouds_00%` to `%fclouds_39%` - Forecast cloud coverage Eg. 99 200 | - `%fpop_00%` to `%fpop_39%` - Probability of precipitation Eg. 100 201 | - `%fpod_00%` to `%fpod_39%` - Part of the day (n - night, d - day) Eg. d 202 | - `%fvis_00%` to `%fvis_39%` - Visibility in feet Eg. 10000 203 | - `%fhum_00%` to `%fhum_39%` - Humidity percentage Eg. 70 204 | - `%ftempmax_00%` to `%ftempmax_39%` - Maximum temperature Eg. 16 205 | - `%ftempmin_00%` to `%ftempmin_39%` - Minimum temperature Eg. 15 206 | - `%fground_00%` to `%fground_39%` - Ground level pressure in millibarsEg. 928 207 | - `%fsea_00%` to `%fsea_39%` - Sea level pressure in millibarsEg. 1007 208 | - `%fdesc_00%` to `%fdesc_39%` - Weather description Eg. Light Rain 209 | - `%fmaindesc_00%` to `%fmaindesc_39%` - Weather main description Eg. Rain 210 | - `%fdescem_00%` to `%fdescem_39%` - Weather description emoji Eg. 🌧️ 211 | - `%ficonurl_00%` to `%ficonurl_39%` - Weather icon URL Eg. https://openweathermap.org/img/wn/10d.png 212 | - `%ficonurl2x_00%` to `%ficonurl2x_39%` - Weather icon URL double size Eg. https://openweathermap.org/img/wn/10d@2x.png 213 | - `%fwindspeed_00%` to `%fwindspeed_39%` - Wind speed in miles per hour Eg. 7 214 | - `%fwindspeedms_00%` to `%fwindspeedms_39%` - Wind speed in meters per second Eg. 2 215 | - `%fwinddeg_00%` to `%fwinddeg_39%` - Wind direction in degrees Eg. 198 216 | - `%fwinddir_00%` to `%fwinddir_39%` - Wind direction Eg. South 217 | - `%fwindgust_00%` to `%fwindgust_39%` - Wind gusts miles per hour Eg. 7 218 | - `%fwindgustms_00%` to `%fwindgustms_39%` - Wind gusts meters per second Eg. 2 219 | - `%next12%` - Forecast list for the next 12 hours Eg... 220 | - 05-26 - 18:00 🌧️ Light Rain Temp: 15 Feels Like: 14 221 | - 05-26 - 21:00 🌧️ Light Rain Temp: 14 Feels Like: 13 222 | - 05-27 - 00:00 ☁️ Overcast Clouds Temp: 12 Feels Like: 11 223 | - 05-27 - 03:00 🌧️ Light Rain Temp: 9 Feels Like: 8 224 | - `%next24%` - Forecast list for the next 24 hours Eg... 225 | - 05-26 - 18:00 🌧️ Light Rain Temp: 15 Feels Like: 14 226 | - 05-26 - 21:00 🌧️ Light Rain Temp: 14 Feels Like: 13 227 | - 05-27 - 00:00 ☁️ Overcast Clouds Temp: 12 Feels Like: 11 228 | - 05-27 - 03:00 🌧️ Light Rain Temp: 9 Feels Like: 8 229 | - 05-27 - 06:00 🌧️ Scattered Clouds Temp: 8 Feels Like: 7 230 | - 05-27 - 09:00 🌥️ Scattered Clouds Temp: 13 Feels Like: 12 231 | - 05-27 - 12:00 🌥️ Broken Clouds Temp: 18 Feels Like: 17 232 | - 05-27 - 15:00 ☁️ Overcast Clouds Temp: 20 Feels Like: 17 233 | - `%next48%` - Forecast list for the next 48 hours Eg... 234 | - 05-26 - 18:00 🌧️ Light Rain Temp: 15 Feels Like: 14 235 | - 05-26 - 21:00 🌧️ Light Rain Temp: 14 Feels Like: 13 236 | - 05-27 - 00:00 ☁️ Overcast Clouds Temp: 12 Feels Like: 11 237 | - 05-27 - 03:00 🌧️ Light Rain Temp: 9 Feels Like: 8 238 | - 05-27 - 06:00 🌧️ Scattered Clouds Temp: 8 Feels Like: 7 239 | - 05-27 - 09:00 🌥️ Scattered Clouds Temp: 13 Feels Like: 12 240 | - 05-27 - 12:00 🌥️ Broken Clouds Temp: 18 Feels Like: 17 241 | - 05-27 - 15:00 ☁️ Overcast Clouds Temp: 20 Feels Like: 17 242 | - 05-27 - 18:00 ☁️ Overcast Clouds Temp: 20 Feels Like: 20 243 | - 05-27 - 21:00 ☁️ Overcast Clouds Temp: 15 Feels Like: 15 244 | - 05-28 - 00:00 ☁️ Overcast Clouds Temp: 14 Feels Like: 13 245 | - 05-28 - 03:00 ☁️ Overcast Clouds Temp: 12 Feels Like: 11 246 | - 05-28 - 06:00 ☁️ Overcast Clouds Temp: 10 Feels Like: 9 247 | - 05-28 - 09:00 🌥️ Broken Clouds Temp: 14 Feels Like: 14 248 | - 05-28 - 12:00 🌥️ Broken Clouds Temp: 19 Feels Like: 18 249 | - 05-28 - 15:00 ☁️ Overcast Clouds Temp: 21 Feels Like: 20 250 | 251 | - ### Weather Placeholder notes 252 | - `%icon%` and `%icon2x%` - This is replaced with the image tag `` This is more useful if it is embedded inside a [div](#div-support) code block. 253 | 254 | - `%wind-gust%` This data is only returned by the API if the condition exists. To make this display the string data only when it exists you can surround it with the caret symbols. 255 | 256 | - For example: `Winds %wind-speed% km/h^ with gusts up to %wind-gust% km/h^` 257 | - With wind gust data this will convert to: `Winds 10 km/h with gusts up to 20 km/h` 258 | - Without wind gust data this will convert to: `Winds 10 km/h` (The text surrounded by carets will be removed) 259 | 260 | ## OpenWeather Plugin Commands 261 | 262 | #### All these commands are available from the command palette and the ribbon icon 263 | 264 | - `OpenWeather: Insert weather string one` - Inserts Weather String One into the current document. 265 | - `OpenWeather: Insert weather string two` - Inserts Weather String Two into the current document. 266 | - `OpenWeather: Insert weather string three` - Inserts Weather String Three into the current document. 267 | - `OpenWeather: Insert weather string four` - Inserts Weather String Four into the current document. 268 | - Note: If text is selected in the current document when these commands are run, it will be replaced by the inserted weather string. 269 | - `OpenWeather: Replace template string` - This will replace all occurences of the strings, `%weather1%`, `%weather2%`, `%weather3%` and `%weather4%` with the corresponding defined weather strings. See also [Template support](#template-support) 270 | 271 | ## Template support 272 | You can place the following strings in your templates and when creating a new document using the template, they will automatically be replaced with the corresponding weather strings. 273 | 274 | - `%weather1%` - Inserts weather string One 275 | - `%weather2%` - Inserts weather string Two 276 | - `%weather3%` - Inserts weather string Three 277 | - `%weather4%` - Inserts weather string Four 278 | 279 | ## DIV support 280 | You can insert the following DIV inside your documents to provide dynamic weather which is updated at the frequency set in the [settings _Update Frequency_](#update-frequency) setting. The `weather_historical_3` is the static temperature at the time the document is created and the `weather_current_1` is dynamic. See [EXAMPLE.md](EXAMPLE.md) for a demonstration of how I use these in my Daily Template. 281 | 282 | 283 | ```html 284 |
%weather3%
285 |
286 | ``` 287 | 288 | You can use the following class's to insert the corresponding weather strings 289 | 290 | - "weather_current_1" Inserts dynamic weather string One 291 | - "weather_current_2" Inserts dynamic weather string Two 292 | - "weather_current_3" Inserts dynamic weather string Three 293 | - "weather_current_4" Inserts dynamic weather string Four 294 | 295 | and... 296 | 297 | - "weather_historical_1" Inserts static weather string One 298 | - "weather_historical_2" Inserts static weather string Two 299 | - "weather_historical_3" Inserts static weather string Three 300 | - "weather_historical_4" Inserts static weather string Four 301 | 302 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 11 | 12 | 13 | ## [1.8.0] 2024-05-26 14 | ### Added 15 | - 5 day forecast 16 | - 33 new placeholders (Times 40 for each time slot - 1320 total) 17 | - 3 complex placeholders `%next12%`, `%next24%`, and `%next48%` 18 | - See the README.md files `Forecast Weather Placeholders` section for details 19 | ### Updated 20 | - README.md 21 | - Added the list of available placeholders 22 | - Updated settings descriptions 23 | 24 | For a full list of changes, please see the projects [Changelog](CHANGELOG.md) file. 25 | 26 | I hope you enjoy using the Plugin, and if you find any bugs, or would like to see a certain feature added, please feel free to contact me. 27 | 28 | Enjoy! William 29 | -------------------------------------------------------------------------------- /dailyNote.css: -------------------------------------------------------------------------------- 1 | /*====================*/ 2 | /* Daily Note Styling */ 3 | /*====================*/ 4 | .daily { 5 | padding-left: 25px !important; 6 | padding-right: 25px !important; 7 | padding-top: 20px !important; 8 | } 9 | 10 | /* Transition Effect Weather One */ 11 | .weather_current_1, 12 | .weather_historical_1 { 13 | transition: 0.3s; 14 | } 15 | 16 | /* Transition Effect Weather Two */ 17 | .weather_current_2, 18 | .weather_historical_2 { 19 | transition: 0.3s; 20 | } 21 | 22 | /* Transition Effect Weather Three */ 23 | .weather_current_3, 24 | .weather_historical_3 { 25 | transition: 0.3s; 26 | } 27 | 28 | /* Transition Effect Weather Four */ 29 | .weather_current_4, 30 | .weather_historical_4 { 31 | transition: 0.3s; 32 | } 33 | 34 | /* Historical & Current weather One, Two, Three and Four common settings */ 35 | .weather_historical_1, .weather_current_1, .weather_historical_2, .weather_current_2, .weather_historical_3, .weather_current_3, .weather_historical_4, .weather_current_4 { 36 | display: flex; 37 | float: left; 38 | clear: left; 39 | align-items: center; 40 | top: 45px; 41 | left: 55px; 42 | white-space: pre; 43 | position: absolute; 44 | font-family: monospace; 45 | font-size: 14pt !important; 46 | margin: 10px 5px; 47 | padding: 10px 20px; 48 | border-radius: 20px; 49 | box-shadow: 3px 3px 2px #414654; 50 | cursor: pointer; 51 | } 52 | 53 | /* Historical weather at top of the document over banner */ 54 | .weather_historical_1, .weather_historical_2, .weather_historical_3, .weather_historical_4 { 55 | color: #d0dce9; 56 | background-color: #0d3d56; 57 | } 58 | 59 | /* Current weather at top of the document over banner */ 60 | .weather_current_1, .weather_current_2, .weather_current_3, .weather_current_4 { 61 | color: #c4caa5; 62 | background-color: #133e2c; 63 | opacity: 0; 64 | } 65 | 66 | /* Show Current weather One on hover */ 67 | .weather_current_1:hover { 68 | opacity: 1; 69 | } 70 | 71 | /* Show Current weather Two on hover */ 72 | .weather_current_2:hover { 73 | opacity: 1; 74 | } 75 | 76 | /* Show Current weather Three on hover */ 77 | .weather_current_3:hover { 78 | opacity: 1; 79 | } 80 | 81 | /* Show Current weather Four on hover */ 82 | .weather_current_4:hover { 83 | opacity: 1; 84 | } 85 | 86 | /* Daily Note Name H6 Styling (YYYY-MM-DD) */ 87 | .daily h6 { 88 | font-size: 1.75em; 89 | color: #4cbf90; 90 | border-width: 1px; 91 | padding-bottom: 3px; 92 | text-align: center; 93 | } 94 | 95 | /* Daily Note Date H5 Styling (🔹 Thursday 🔹 10th November 🔹 2022 🔹) */ 96 | .daily h5 { 97 | font-size: 1.25em; 98 | color: #23a49d; 99 | border-width: 1px; 100 | padding-bottom: 3px; 101 | text-align: center; 102 | } 103 | 104 | /* Hide the frontmatter for Daily Notes 105 | .daily .frontmatter-container { 106 | display: none; 107 | } 108 | */ 109 | 110 | /*==============================*/ 111 | /* Display Daily Notes Calendar */ 112 | /*==============================*/ 113 | .calendar table { 114 | top: 30px; 115 | right: 30px; 116 | position: absolute; 117 | background-color: #0d3d56; 118 | border-radius: 10px; 119 | } 120 | 121 | .calendar thead { 122 | background-color: #147980; 123 | } 124 | 125 | .calendar th { 126 | color: goldenrod !important; 127 | } 128 | 129 | .calendar table th:first-of-type { 130 | border-top-left-radius: 6px; 131 | } 132 | .calendar table th:last-of-type { 133 | border-top-right-radius: 6px; 134 | } 135 | 136 | .calendar .internal-link { 137 | color: rgb(215, 146, 27); 138 | } 139 | 140 | .calendar .internal-link.is-unresolved { 141 | color: silver; 142 | } 143 | 144 | .calendar td strong { 145 | border-radius: 50%; 146 | padding: 3px; 147 | border: 2px solid #229ecf; 148 | color: black !important; 149 | } 150 | 151 | .calendar mark { 152 | /* color: #fd0707 !important; */ 153 | background: #0d3d56; 154 | } 155 | 156 | .calendar strong .internal-link{ 157 | color: #23bca5 !important; 158 | /* background: #0d3d56; */ 159 | } 160 | 161 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/collab', 25 | '@codemirror/commands', 26 | '@codemirror/language', 27 | '@codemirror/lint', 28 | '@codemirror/search', 29 | '@codemirror/state', 30 | '@codemirror/view', 31 | '@lezer/common', 32 | '@lezer/highlight', 33 | '@lezer/lr', 34 | ...builtins], 35 | format: 'cjs', 36 | watch: !prod, 37 | target: 'es2018', 38 | logLevel: "info", 39 | sourcemap: prod ? false : 'inline', 40 | treeShaking: true, 41 | outfile: 'main.js', 42 | }).catch(() => process.exit(1)); 43 | -------------------------------------------------------------------------------- /getCurrentWeather.ts: -------------------------------------------------------------------------------- 1 | // • getCurrentWeather - Returns Air Qulity Index From API • 2 | export default async function getCurrentWeather(key:any, latitude:any, longitude:any, language:any, units:any, format:any) { 3 | let weatherData; 4 | let weatherString; 5 | let url; 6 | let aqiNumber; 7 | let aqiString; 8 | 9 | // • Get Air Quality Index From API • 10 | let urlAQI = `https://api.openweathermap.org/data/2.5/air_pollution?lat=${latitude}&lon=${longitude}&appid=${key}` 11 | if (latitude.length > 0 && longitude.length > 0 && key.length > 0) { 12 | let reqAQI = await fetch(urlAQI); 13 | let jsonAQI = await reqAQI.json(); 14 | if (jsonAQI.cod) { 15 | aqiNumber = 0; 16 | aqiString = "Air Quality Index is Not Available"; 17 | } else { 18 | const aqiStringsArr = ['Good', 'Fair', 'Moderate', 'Poor', 'Very Poor']; 19 | aqiNumber = jsonAQI.list[0].main.aqi; 20 | aqiString = aqiStringsArr[aqiNumber-1] 21 | }; 22 | } else { 23 | aqiNumber = 0; 24 | aqiString = "Air Quality Index is Not Available"; 25 | }; 26 | 27 | // • Get Current Weather From API • 28 | if (latitude.length > 0 && longitude.length > 0) { 29 | url = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&lang=${language}&appid=${key}&units=${units}`; 30 | } else { 31 | url = `https://api.openweathermap.org/data/2.5/weather?q=${location}&lang=${language}&appid=${key}&units=${units}`; 32 | }; 33 | let req = await fetch(url); 34 | let json = await req.json(); 35 | //console.log('json:', json); 36 | if (json.cod != 200) { 37 | weatherString = "Error Code "+json.cod+": "+json.message; 38 | return weatherString; 39 | }; 40 | 41 | // • Get Current Weather Data We Want • 42 | let conditions = json.weather[0].description; 43 | let id = json.weather[0].id; 44 | let conditionsEm = ''; 45 | if (id > 199 && id < 300) { 46 | conditionsEm = '⛈️'; 47 | }; 48 | if (id > 299 && id < 500) { 49 | conditionsEm = '🌦️'; 50 | }; 51 | if (id > 499 && id < 600) { 52 | conditionsEm = '🌧️'; 53 | }; 54 | if (id > 599 && id < 700) { 55 | conditionsEm = '❄️'; 56 | }; 57 | if (id > 699 && id < 800) { 58 | conditionsEm = '🌫️'; 59 | }; 60 | if (id == 771) { 61 | conditionsEm = '🌀'; 62 | }; 63 | if (id == 781) { 64 | conditionsEm = '🌪️'; 65 | }; 66 | if (id == 800) { 67 | conditionsEm = '🔆'; 68 | }; 69 | if (id > 800 && id < 804) { 70 | conditionsEm = '🌥️'; 71 | }; 72 | if (id == 804) { 73 | conditionsEm = '☁️'; 74 | }; 75 | conditions = conditions.replace(/^\w|\s\w/g, (c: string) => c.toUpperCase()); 76 | let iconName = json.weather[0].icon; 77 | const iconApi = await fetch('https://openweathermap.org/img/wn/' + iconName + '.png'); 78 | let iconUrl = iconApi.url; 79 | const iconApi2x = await fetch('https://openweathermap.org/img/wn/' + iconName + '@2x.png'); 80 | let iconUrl2x = iconApi2x.url; 81 | let temp = json.main.temp; 82 | temp = Math.round(temp); 83 | let feelsLike = json.main.feels_like; 84 | feelsLike = Math.round(feelsLike); 85 | let tempMin = json.main.temp_min; 86 | tempMin = Math.round(tempMin); 87 | let tempMax = json.main.temp_max; 88 | tempMax = Math.round(tempMax); 89 | let pressure = json.main.pressure; 90 | let humidity = json.main.humidity; 91 | let seaLevel = json.main.sea_level; 92 | let groundLevel = json.main.grnd_level; 93 | let visibility = json.visibility; 94 | // Winds 95 | let windSpeed = json.wind.speed; 96 | let windSpeedms = json.wind.speed; 97 | if (this.units == "metric") { 98 | windSpeed = Math.round(windSpeed*3.6); 99 | windSpeedms = Math.round(windSpeedms); 100 | } else { 101 | windSpeed = Math.round(windSpeed); 102 | } 103 | let windDirection = json.wind.deg; 104 | const directions = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']; 105 | windDirection = directions[Math.round(windDirection / 45) % 8]; 106 | let windGust = json.wind.gust; 107 | if (windGust != undefined) { 108 | if (this.units == "metric") { 109 | windGust = Math.round(windGust*3.6); 110 | } else { 111 | windGust = Math.round(windGust); 112 | } 113 | } else { 114 | windGust = "N/A"; 115 | } 116 | 117 | // Cloud cover 118 | let clouds = json.clouds.all; 119 | // Precipitation 120 | let rain1h; 121 | let rain3h; 122 | let snow1h; 123 | let snow3h; 124 | let precipitation1h; 125 | let precipitation3h; 126 | // Precipitation - Rain 127 | if (json.rain != undefined) { 128 | let rainObj = json.rain; 129 | let keys = Object.keys(rainObj); 130 | let values = Object.values(rainObj); 131 | if (keys[0] === "1h") { 132 | rain1h = values[0]; 133 | } else if (keys[0] === "3h") { 134 | rain3h = values[0]; 135 | } 136 | if (keys.length > 1) { 137 | if (keys[1] === "1h") { 138 | rain1h = values[1]; 139 | } else if (keys[1] === "3h") { 140 | rain3h = values[1]; 141 | } 142 | } 143 | } else { 144 | rain1h = 0; 145 | rain3h = 0; 146 | } 147 | if (rain1h === undefined) {rain1h = 0}; 148 | if (rain3h === undefined) {rain3h = 0}; 149 | // Precipitation - Snow 150 | if (json.snow != undefined) { 151 | let snowObj = json.snow; 152 | let keys = Object.keys(snowObj); 153 | let values = Object.values(snowObj); 154 | if (keys[0] === "1h") { 155 | snow1h = values[0]; 156 | } else if (keys[0] === "3h") { 157 | snow3h = values[0]; 158 | } 159 | if (keys.length > 1) { 160 | if (keys[1] === "1h") { 161 | snow1h = values[1]; 162 | } else if (keys[1] === "3h") { 163 | snow3h = values[1]; 164 | } 165 | } 166 | } else { 167 | snow1h = 0; 168 | snow3h = 0; 169 | } 170 | if (snow1h === undefined) {snow1h = 0}; 171 | if (snow3h === undefined) {snow3h = 0}; 172 | precipitation1h = rain1h || snow1h; 173 | precipitation3h = rain3h || snow3h; 174 | 175 | // Date/Time of last weather update from API 176 | let dt = json.dt; 177 | let a = new Date(dt * 1000); 178 | const months1 = ["1","2","3","4","5","6","7","8","9","10","11","12"]; 179 | const months2 = ["01","02","03","04","05","06","07","08","09","10","11","12"]; 180 | const months3 = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; 181 | const months4 = ["January","February","March","April","May","June","July","August","September","October","November","December"]; 182 | let year1 = a.getFullYear(); 183 | let year2str = String(year1).slice(-2); 184 | let year2 = Number(year2str); 185 | let month1 = months1[a.getMonth()]; 186 | let month2 = months2[a.getMonth()]; 187 | let month3 = months3[a.getMonth()]; 188 | let month4 = months4[a.getMonth()]; 189 | let date1 = a.getDate(); 190 | let date2 = a.getDate() < 10 ? "0" + a.getDate() : a.getDate(); 191 | let ampm1 = "AM"; 192 | let ampm2 = "am"; 193 | if (a.getHours() > 11) { 194 | ampm1 = "PM"; 195 | ampm2 = "pm" 196 | } 197 | let hour1 = a.getHours(); 198 | let hour2 = a.getHours(); 199 | if (a.getHours() > 12) { 200 | hour2 = a.getHours() - 12; 201 | } 202 | if (a.getHours() == 0) { 203 | hour1 = 12; 204 | hour2 = 12; 205 | } 206 | let min = a.getMinutes() < 10 ? "0" + a.getMinutes() : a.getMinutes(); 207 | let sec = a.getSeconds() < 10 ? "0" + a.getSeconds() : a.getSeconds(); 208 | // Sunrise and Sunset times 209 | let sr = json.sys.sunrise; 210 | let b = new Date(sr * 1000); 211 | let srhour = b.getHours() < 10 ? '0' + b.getHours() : b.getHours(); 212 | let srmin = b.getMinutes() < 10 ? '0' + b.getMinutes() : b.getMinutes(); 213 | let srsec = b.getSeconds() < 10 ? '0' + b.getSeconds() : b.getSeconds(); 214 | let sunrise = srhour + ':' + srmin + ':' + srsec; 215 | let ss = json.sys.sunset; 216 | let c = new Date(ss * 1000); 217 | let sshour = c.getHours() < 10 ? '0' + c.getHours() : c.getHours(); 218 | let ssmin = c.getMinutes() < 10 ? '0' + c.getMinutes() : c.getMinutes(); 219 | let sssec = c.getSeconds() < 10 ? '0' + c.getSeconds() : c.getSeconds(); 220 | let sunset = sshour + ':' + ssmin + ':' + sssec; 221 | // Location Name 222 | let name = json.name; 223 | // Latitude and Longitude 224 | let lat = json.coord.lat; 225 | let lon = json.coord.lon; 226 | 227 | // getWeather - Create weather data object 228 | weatherData = { 229 | "status": "ok", 230 | "conditions": conditions, 231 | "conditionsEm": conditionsEm, 232 | "icon": iconUrl, 233 | "icon2x": iconUrl2x, 234 | "temp": temp, 235 | "feelsLike": feelsLike, 236 | "tempMin": tempMin, 237 | "tempMax": tempMax, 238 | "pressure": pressure, 239 | "humidity": humidity, 240 | "seaLevel": seaLevel, 241 | "groundLevel": groundLevel, 242 | "visibility": visibility, 243 | "windSpeed": windSpeed, 244 | "windSpeedms": windSpeedms, 245 | "windDirection": windDirection, 246 | "windGust": windGust, 247 | "clouds": clouds, 248 | "rain1h": rain1h, 249 | "rain3h": rain3h, 250 | "snow1h": snow1h, 251 | "snow3h": snow3h, 252 | "precipitation1h": precipitation1h, 253 | "precipitation3h": precipitation3h, 254 | "year1": year1, 255 | "year2": year2, 256 | "month1": month1, 257 | "month2": month2, 258 | "month3": month3, 259 | "month4": month4, 260 | "date1": date1, 261 | "date2": date2, 262 | "ampm1": ampm1, 263 | "ampm2": ampm2, 264 | "hour1": hour1, 265 | "hour2": hour2, 266 | "min": min, 267 | "sec": sec, 268 | "sunrise": sunrise, 269 | "sunset": sunset, 270 | "name": name, 271 | "latitude": lat, 272 | "longitude": lon, 273 | "aqiNum": aqiNumber, 274 | "aqiStr": aqiString 275 | } 276 | 277 | // getWeather - Create Formatted weather string 278 | weatherString = format.replace(/%desc%/gmi, weatherData.conditions); 279 | weatherString = weatherString.replace(/%desc-em%/gmi, weatherData.conditionsEm); 280 | weatherString = weatherString.replace(/%icon%/gmi, ``); 281 | weatherString = weatherString.replace(/%icon2x%/gmi, ``); 282 | weatherString = weatherString.replace(/%temp%/gmi, weatherData.temp); 283 | weatherString = weatherString.replace(/%feels%/gmi, weatherData.feelsLike); 284 | weatherString = weatherString.replace(/%tempmin%/gmi, weatherData.tempMin); 285 | weatherString = weatherString.replace(/%tempmax%/gmi, weatherData.tempMax); 286 | weatherString = weatherString.replace(/%pressure%/gmi, weatherData.pressure); 287 | weatherString = weatherString.replace(/%humidity%/gmi, weatherData.humidity); 288 | weatherString = weatherString.replace(/%pressure-sl%/gmi, weatherData.seaLevel); 289 | weatherString = weatherString.replace(/%pressure-gl%/gmi, weatherData.groundLevel); 290 | weatherString = weatherString.replace(/%visibility%/gmi, weatherData.visibility); 291 | weatherString = weatherString.replace(/%wind-speed%/gmi, weatherData.windSpeed); 292 | weatherString = weatherString.replace(/%wind-speed-ms%/gmi, weatherData.windSpeedms); 293 | weatherString = weatherString.replace(/%wind-dir%/gmi, weatherData.windDirection); 294 | if (weatherData.windGust == "N/A") { 295 | weatherString = weatherString.replace(/\^.+\^/gmi, ""); 296 | } else { 297 | weatherString = weatherString.replace(/%wind-gust%/gmi, weatherData.windGust); 298 | weatherString = weatherString.replace(/\^(.+)\^/gmi, "$1"); 299 | } 300 | weatherString = weatherString.replace(/%clouds%/gmi, `${weatherData.clouds}`); 301 | weatherString = weatherString.replace(/%rain1h%/gmi, `${weatherData.rain1h}`); 302 | weatherString = weatherString.replace(/%rain3h%/gmi, `${weatherData.rain3h}`); 303 | weatherString = weatherString.replace(/%snow1h%/gmi, `${weatherData.snow1h}`); 304 | weatherString = weatherString.replace(/%snow3h%/gmi, `${weatherData.snow3h}`); 305 | weatherString = weatherString.replace(/%precipitation1h%/gmi, `${weatherData.precipitation1h}`); 306 | weatherString = weatherString.replace(/%precipitation3h%/gmi, `${weatherData.precipitation3h}`); 307 | weatherString = weatherString.replace(/%dateYear1%/gmi, `${weatherData.year1}`); 308 | weatherString = weatherString.replace(/%dateYear2%/gmi, `${weatherData.year2}`); 309 | weatherString = weatherString.replace(/%dateMonth1%/gmi, `${weatherData.month1}`); 310 | weatherString = weatherString.replace(/%dateMonth2%/gmi, `${weatherData.month2}`); 311 | weatherString = weatherString.replace(/%dateMonth3%/gmi, `${weatherData.month3}`); 312 | weatherString = weatherString.replace(/%dateMonth4%/gmi, `${weatherData.month4}`); 313 | weatherString = weatherString.replace(/%dateDay1%/gmi, `${weatherData.date1}`); 314 | weatherString = weatherString.replace(/%dateDay2%/gmi, `${weatherData.date2}`); 315 | weatherString = weatherString.replace(/%ampm1%/gmi, `${weatherData.ampm1}`); 316 | weatherString = weatherString.replace(/%ampm2%/gmi, `${weatherData.ampm2}`); 317 | weatherString = weatherString.replace(/%timeH1%/gmi, `${weatherData.hour1}`); 318 | weatherString = weatherString.replace(/%timeH2%/gmi, `${weatherData.hour2}`); 319 | weatherString = weatherString.replace(/%timeM%/gmi, `${weatherData.min}`); 320 | weatherString = weatherString.replace(/%timeS%/gmi, `${weatherData.sec}`); 321 | weatherString = weatherString.replace(/%sunrise%/gmi, `${weatherData.sunrise}`); 322 | weatherString = weatherString.replace(/%sunset%/gmi, `${weatherData.sunset}`); 323 | weatherString = weatherString.replace(/%name%/gmi, `${weatherData.name}`); 324 | weatherString = weatherString.replace(/%latitude%/gmi, `${weatherData.latitude}`); 325 | weatherString = weatherString.replace(/%longitude%/gmi, `${weatherData.longitude}`); 326 | weatherString = weatherString.replace(/%aqinumber%/gmi, `${weatherData.aqiNum}`); 327 | weatherString = weatherString.replace(/%aqistring%/gmi, `${weatherData.aqiStr}`); 328 | 329 | return weatherString; 330 | } 331 | 332 | // // • getCardinalDirection - Converts the wind direction in degrees to text and returns the string value • 333 | // getCardinalDirection(angle: number) { 334 | // const directions = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']; 335 | // return directions[Math.round(angle / 45) % 8]; 336 | // } 337 | -------------------------------------------------------------------------------- /images/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Demo.gif -------------------------------------------------------------------------------- /images/Format1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Format1-1.png -------------------------------------------------------------------------------- /images/Format2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Format2-1.png -------------------------------------------------------------------------------- /images/Format3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Format3-1.png -------------------------------------------------------------------------------- /images/Format3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Format3-2.png -------------------------------------------------------------------------------- /images/Format4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Format4-1.png -------------------------------------------------------------------------------- /images/Format4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Format4-2.png -------------------------------------------------------------------------------- /images/Statusbar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willasm/obsidian-open-weather/f457f6c6f02e1d5f1455176e6b3f04fbd1ec67d6/images/Statusbar-1.png -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting, TextAreaComponent, TAbstractFile, TFolder, SuggestModal, Platform, FileSystemAdapter } from 'obsidian'; 2 | import getCurrentWeather from './getCurrentWeather'; 3 | import getForecastWeather from './getForecastWeather'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | //const path = require('path'); 7 | 8 | let displayErrorMsg = true; 9 | 10 | interface OpenWeatherSettings { 11 | location: string; 12 | latitude: string; 13 | longitude: string; 14 | key: string; 15 | units: string; 16 | language: string; 17 | excludeFolder: string; 18 | weatherFormat1: string; 19 | weatherFormat2: string; 20 | weatherFormat3: string; 21 | weatherFormat4: string; 22 | statusbarActive: boolean; 23 | weatherFormatSB: string; 24 | statusbarUpdateFreq: string; 25 | } 26 | 27 | const DEFAULT_SETTINGS: OpenWeatherSettings = { 28 | location: '', 29 | latitude: '', 30 | longitude: '', 31 | key: '', 32 | units: 'metric', 33 | language: 'en', 34 | excludeFolder: '', 35 | weatherFormat1: '%desc% • Current Temp: %temp%°C • Feels Like: %feels%°C\n', 36 | weatherFormat2: '%name%: %dateMonth4% %dateDay2% - %timeH2%:%timeM% %ampm1%\nCurrent Temp: %temp%°C • Feels Like: %feels%°C\nWind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^\nSunrise: %sunrise% • Sunset: %sunset%\n', 37 | weatherFormat3: `%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc% 38 |  Recorded Temp: %temp% • Felt like: %feels% 39 |  Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^ 40 |  Sunrise: %sunrise% • Sunset: %sunset%`, 41 | weatherFormat4: `%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc% 42 |  Current Temp: %temp% • Feels like: %feels% 43 |  Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^ 44 |  Sunrise: %sunrise% • Sunset: %sunset%`, 45 | // weatherFormat3: '%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Recorded Temp: %temp% • Felt like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%', 46 | // weatherFormat4: '%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Current Temp: %temp% • Feels like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%', 47 | statusbarActive: true, 48 | weatherFormatSB: ' | %desc% | Current Temp: %temp%°C | Feels Like: %feels%°C | ', 49 | statusbarUpdateFreq: "15" 50 | } 51 | 52 | // ╭──────────────────────────────────────────────────────────────────────────────╮ 53 | // │ ● Class FormatWeather ● │ 54 | // │ │ 55 | // │ • Get Current Weather From OpenWeather API and Return a Formatted String • │ 56 | // ╰──────────────────────────────────────────────────────────────────────────────╯ 57 | class FormatWeather { 58 | location: String; 59 | latitude: String; 60 | longitude: String; 61 | key: string 62 | units: string 63 | language: string 64 | format: string 65 | 66 | constructor(location: string, latitude: string, longitude: string, key: string, units: string, language: string, format: string) { 67 | this.location = location; 68 | this.latitude = latitude; 69 | this.longitude = longitude; 70 | this.key = key; 71 | this.units = units; 72 | this.language = language; 73 | this.format = format; 74 | } 75 | 76 | // • getWeather - Get the weather data from the OpenWeather API • 77 | async getWeather():Promise { 78 | let weatherStr = await getCurrentWeather(this.key, this.latitude, this.longitude, this.language, this.units, this.format); 79 | let forecastWeatherStr = await getForecastWeather(this.key, this.latitude, this.longitude, this.language, this.units, weatherStr); 80 | return(forecastWeatherStr); 81 | } 82 | 83 | // • getWeatherString - Returns a formatted weather string • 84 | async getWeatherString() { 85 | try { 86 | let weatherString = await this.getWeather(); 87 | return weatherString; 88 | } catch (error) { 89 | console.log('Error',error); 90 | let weatherString = "Failed to fetch weather data"; 91 | return weatherString; 92 | } 93 | } 94 | 95 | } 96 | 97 | // ╭──────────────────────────────────────────────────────────────────────────────╮ 98 | // │ ● Class OpenWeather ● │ 99 | // │ │ 100 | // │ • The Plugin class defines the lifecycle of a plugin • │ 101 | // ╰──────────────────────────────────────────────────────────────────────────────╯ 102 | export default class OpenWeather extends Plugin { 103 | settings: OpenWeatherSettings; 104 | statusBar: HTMLElement; 105 | divEl: HTMLElement; 106 | plugin: any; 107 | 108 | // • onload - Configure resources needed by the plugin • 109 | async onload() { 110 | 111 | // onload - Load settings 112 | await this.loadSettings(); 113 | // weatherFormat3: '%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Recorded Temp: %temp% • Felt like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%', 114 | // weatherFormat4: '%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Current Temp: %temp% • Feels like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%', 115 | if (this.settings.weatherFormat3 == '%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Recorded Temp: %temp% • Felt like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%') { 116 | this.settings.weatherFormat3 = `%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc% 117 |  Recorded Temp: %temp% • Felt like: %feels% 118 |  Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^ 119 |  Sunrise: %sunrise% • Sunset: %sunset%`; 120 | await this.saveSettings(); 121 | }; 122 | if (this.settings.weatherFormat4 == '%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc%
 Recorded Temp: %temp% • Felt like: %feels%
 Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^
 Sunrise: %sunrise% • Sunset: %sunset%') { 123 | this.settings.weatherFormat4 = `%icon% %dateMonth4% %dateDay2% %dateYear1% • %timeH2%:%timeM% %ampm1% • %desc% 124 |  Recorded Temp: %temp% • Felt like: %feels% 125 |  Wind: %wind-speed% km/h from the %wind-dir%^ with gusts up to %wind-gust% km/h^ 126 |  Sunrise: %sunrise% • Sunset: %sunset%`; 127 | await this.saveSettings(); 128 | }; 129 | let configDir = app.vault.configDir; 130 | let dataJsonPath = ""; 131 | let dataJsonFilePath = ""; 132 | let adapter = app.vault.adapter; 133 | if (adapter instanceof FileSystemAdapter) { 134 | dataJsonPath = path.join(adapter.getBasePath(),configDir,'plugins','open-weather'); 135 | dataJsonFilePath = path.join(adapter.getBasePath(),configDir,'plugins','open-weather','data.json'); 136 | }; 137 | let dataJson = await JSON.parse(fs.readFileSync(dataJsonFilePath, 'utf-8')); 138 | console.log('>>>>>>> TESTING\n'); 139 | //console.log('settings:\n', this.settings); 140 | if (dataJson.weatherString1) { 141 | fs.renameSync(dataJsonFilePath, path.join(dataJsonPath, 'data.json.bkp')); 142 | delete dataJson.weatherString1 143 | delete dataJson.weatherString2 144 | delete dataJson.weatherString3 145 | delete dataJson.weatherString4 146 | delete dataJson.weatherStringSB 147 | fs.writeFileSync(dataJsonFilePath, JSON.stringify(dataJson, null, 2)); 148 | await this.loadSettings(); 149 | await this.saveSettings(); 150 | //console.log('dataJson:\n', dataJson); 151 | }; 152 | //await this.onPick.bind(this, this.plugin, this.settings); 153 | //OpenWeather.prototype.onPick.bind(this.settings.location); 154 | //await this.onload.bind(this, this.plugin, this.settings); 155 | 156 | // onload - This creates an icon in the left ribbon 157 | this.addRibbonIcon('thermometer-snowflake', 'OpenWeather', (evt: MouseEvent) => { 158 | // Called when the user clicks the icon. 159 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 160 | if (!view) { 161 | new Notice("Open a Markdown file first."); 162 | return; 163 | } 164 | new InsertWeatherPicker(this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat1, this.settings.weatherFormat2, this.settings.weatherFormat3, this.settings.weatherFormat4).open(); 165 | }); 166 | 167 | // onload - This adds a status bar item to the bottom of the app - Does not work on mobile apps 168 | this.statusBar = this.addStatusBarItem(); 169 | if (this.settings.statusbarActive) { 170 | if (this.settings.key.length == 0 || this.settings.location.length == 0) { 171 | if (displayErrorMsg) { 172 | new Notice("OpenWeather plugin settings are undefined, check your settings.", 8000) 173 | this.statusBar.setText(''); 174 | console.log('Err:', displayErrorMsg); 175 | displayErrorMsg = false; 176 | } 177 | } else { 178 | let wstr = new FormatWeather(this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormatSB); 179 | let weatherStr = await wstr.getWeatherString(); 180 | if (weatherStr.length == 0) {return}; 181 | this.statusBar.setText(weatherStr); 182 | } 183 | } else { 184 | this.statusBar.setText(''); 185 | } 186 | 187 | // onload - Replace template strings 188 | this.addCommand ({ 189 | id: 'replace-template-string', 190 | name: 'Replace template strings', 191 | editorCallback: async (editor: Editor, view: MarkdownView) => { 192 | if (this.settings.weatherFormat1.length > 0) { 193 | if (view.data.contains("%weather1%")) { 194 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat1); 195 | let weatherStr = await wstr.getWeatherString(); 196 | if (weatherStr.length == 0) {return}; 197 | let doc = editor.getValue().replace(/%weather1%/gmi, weatherStr); 198 | editor.setValue(doc); 199 | } 200 | } 201 | if (this.settings.weatherFormat2.length > 0) { 202 | if (view.data.contains("%weather2%")) { 203 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat2); 204 | let weatherStr = await wstr.getWeatherString(); 205 | if (weatherStr.length == 0) {return}; 206 | let doc = editor.getValue().replace(/%weather2%/gmi, weatherStr); 207 | editor.setValue(doc); 208 | } 209 | } 210 | if (this.settings.weatherFormat3.length > 0) { 211 | if (view.data.contains("%weather3%")) { 212 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat3); 213 | let weatherStr = await wstr.getWeatherString(); 214 | if (weatherStr.length == 0) {return}; 215 | let doc = editor.getValue().replace(/%weather3%/gmi, weatherStr); 216 | editor.setValue(doc); 217 | } 218 | } 219 | if (this.settings.weatherFormat4.length > 0) { 220 | if (view.data.contains("%weather4%")) { 221 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat4); 222 | let weatherStr = await wstr.getWeatherString(); 223 | if (weatherStr.length == 0) {return}; 224 | let doc = editor.getValue().replace(/%weather4%/gmi, weatherStr); 225 | editor.setValue(doc); 226 | } 227 | } 228 | } 229 | }); 230 | 231 | // onload - Insert weather string one 232 | this.addCommand ({ 233 | id: 'insert-string-one', 234 | name: 'Insert weather string one', 235 | editorCallback: async (editor: Editor, view: MarkdownView) => { 236 | if (this.settings.weatherFormat1.length > 0) { 237 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat1); 238 | let weatherStr = await wstr.getWeatherString(); 239 | if (weatherStr.length == 0) {return}; 240 | editor.replaceSelection(`${weatherStr}`); 241 | } else { 242 | new Notice('Weather string 1 is undefined! Please add a definition for it in the OpenWeather plugin settings.', 5000); 243 | } 244 | } 245 | }); 246 | 247 | // onload - Insert weather string two 248 | this.addCommand ({ 249 | id: 'insert-string-two', 250 | name: 'Insert weather string two', 251 | editorCallback: async (editor: Editor, view: MarkdownView) => { 252 | if (this.settings.weatherFormat2.length > 0) { 253 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat2); 254 | let weatherStr = await wstr.getWeatherString(); 255 | if (weatherStr.length == 0) {return}; 256 | editor.replaceSelection(`${weatherStr}`); 257 | } else { 258 | new Notice('Weather string 2 is undefined! Please add a definition for it in the OpenWeather plugin settings.', 5000); 259 | } 260 | } 261 | }); 262 | 263 | // onload - Insert weather string three 264 | this.addCommand ({ 265 | id: 'insert-string-three', 266 | name: 'Insert weather string three', 267 | editorCallback: async (editor: Editor, view: MarkdownView) => { 268 | if (this.settings.weatherFormat3.length > 0) { 269 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat3); 270 | let weatherStr = await wstr.getWeatherString(); 271 | if (weatherStr.length == 0) {return}; 272 | editor.replaceSelection(`${weatherStr}`); 273 | } else { 274 | new Notice('Weather string 3 is undefined! Please add a definition for it in the OpenWeather plugin settings.', 5000); 275 | } 276 | } 277 | }); 278 | 279 | // onload - Insert weather string four 280 | this.addCommand ({ 281 | id: 'insert-string-four', 282 | name: 'Insert weather string four', 283 | editorCallback: async (editor: Editor, view: MarkdownView) => { 284 | if (this.settings.weatherFormat4.length > 0) { 285 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat4); 286 | let weatherStr = await wstr.getWeatherString(); 287 | if (weatherStr.length == 0) {return}; 288 | editor.replaceSelection(`${weatherStr}`); 289 | } else { 290 | new Notice('Weather string 4 is undefined! Please add a definition for it in the OpenWeather plugin settings.', 5000); 291 | } 292 | } 293 | }); 294 | 295 | // onload - This adds a settings tab so the user can configure various aspects of the plugin 296 | this.addSettingTab(new OpenWeatherSettingsTab(this.app, this, this.settings)); 297 | 298 | // onload - registerEvent - 'file-open' 299 | this.registerEvent(this.app.workspace.on('file-open', async (file) => { 300 | if (file) { 301 | await new Promise(r => setTimeout(r, 2000)); // Wait for Templater to do its thing 302 | await this.replaceTemplateStrings(); 303 | await this.updateCurrentWeatherDiv(); 304 | } 305 | })); 306 | 307 | // onload - registerEvent - 'layout-change' 308 | this.registerEvent(this.app.workspace.on('layout-change', async () => { 309 | await new Promise(r => setTimeout(r, 2000)); // Wait for Templater to do its thing 310 | await this.replaceTemplateStrings(); 311 | await this.updateCurrentWeatherDiv(); 312 | })); 313 | 314 | // onload - registerEvent - 'resolved' 315 | this.registerEvent(this.app.metadataCache.on('resolved', async () => { 316 | await this.replaceTemplateStrings(); 317 | await this.updateCurrentWeatherDiv(); 318 | })); 319 | 320 | // onload - When registering intervals, this function will automatically clear the interval when the plugin is disabled 321 | let updateFreq = this.settings.statusbarUpdateFreq; 322 | this.registerInterval(window.setInterval(() => this.updateWeather(), Number(updateFreq) * 60 * 1000)); 323 | this.registerInterval(window.setInterval(() => this.updateCurrentWeatherDiv(), Number(updateFreq) * 60 * 1000)); 324 | 325 | } 326 | 327 | // • updateCurrentWeatherDiv - Update DIV's with current weather • 328 | async updateCurrentWeatherDiv() { 329 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 330 | if (!view) return; 331 | if(document.getElementsByClassName('weather_current_1').length === 1) { 332 | const divEl = document.getElementsByClassName('weather_current_1')[0]; 333 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat1); 334 | let weatherStr = await wstr.getWeatherString(); 335 | if (weatherStr.length == 0) {return}; 336 | divEl.innerHTML = weatherStr; 337 | } 338 | if(document.getElementsByClassName('weather_current_2').length === 1) { 339 | const divEl = document.getElementsByClassName('weather_current_2')[0]; 340 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat2); 341 | let weatherStr = await wstr.getWeatherString(); 342 | if (weatherStr.length == 0) {return}; 343 | divEl.innerHTML = weatherStr; 344 | } 345 | if(document.getElementsByClassName('weather_current_3').length === 1) { 346 | const divEl = document.getElementsByClassName('weather_current_3')[0]; 347 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat3); 348 | let weatherStr = await wstr.getWeatherString(); 349 | divEl.innerHTML = weatherStr; 350 | } 351 | if(document.getElementsByClassName('weather_current_4').length === 1) { 352 | const divEl = document.getElementsByClassName('weather_current_4')[0]; 353 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat4); 354 | let weatherStr = await wstr.getWeatherString(); 355 | if (weatherStr.length == 0) {return}; 356 | divEl.innerHTML = weatherStr; 357 | } 358 | } 359 | 360 | // • replaceTemplateStrings - Replace any template strings in current file • 361 | async replaceTemplateStrings() { 362 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 363 | if (!view) return; 364 | const file = app.workspace.getActiveFile(); 365 | if (view.file.parent.path.includes(this.settings.excludeFolder)) return; // Ignore this folder and any subfolders for Template String Replacement 366 | let editor = view.getViewData(); 367 | if (editor == null) return; 368 | if (this.settings.weatherFormat1.length > 0) { 369 | if (editor.contains("%weather1%")) { 370 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat1); 371 | let weatherStr = await wstr.getWeatherString(); 372 | if (weatherStr.length == 0) {return}; 373 | editor = editor.replace(/%weather1%/gmi, weatherStr); 374 | file?.vault.modify(file, editor); 375 | } 376 | } 377 | if (this.settings.weatherFormat2.length > 0) { 378 | if (editor.contains("%weather2%")) { 379 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat2); 380 | let weatherStr = await wstr.getWeatherString(); 381 | if (weatherStr.length == 0) {return}; 382 | editor = editor.replace(/%weather2%/gmi, weatherStr); 383 | file?.vault.modify(file, editor); 384 | } 385 | } 386 | if (this.settings.weatherFormat3.length > 0) { 387 | if (editor.contains("%weather3%")) { 388 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat3); 389 | let weatherStr = await wstr.getWeatherString(); 390 | if (weatherStr.length == 0) {return}; 391 | editor = editor.replace(/%weather3%/gmi, weatherStr); 392 | file?.vault.modify(file, editor); 393 | } 394 | } 395 | if (this.settings.weatherFormat4.length > 0) { 396 | if (editor.contains("%weather4%")) { 397 | let wstr = new FormatWeather (this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormat4); 398 | let weatherStr = await wstr.getWeatherString(); 399 | if (weatherStr.length == 0) {return}; 400 | editor = editor.replace(/%weather4%/gmi, weatherStr); 401 | file?.vault.modify(file, editor); 402 | } 403 | } 404 | } 405 | 406 | // • updateWeather - Get weather information from OpenWeather API • 407 | async updateWeather() { 408 | if (this.settings.statusbarActive) { 409 | if (this.settings.key.length == 0 || this.settings.location.length == 0) { 410 | if (displayErrorMsg) { 411 | new Notice("OpenWeather plugin settings are undefined, check your settings."); 412 | this.statusBar.setText(''); 413 | displayErrorMsg = false; 414 | } 415 | } else { 416 | let wstr = new FormatWeather(this.settings.location, this.settings.latitude, this.settings.longitude, this.settings.key, this.settings.units, this.settings.language, this.settings.weatherFormatSB); 417 | let weatherStr = await wstr.getWeatherString(); 418 | if (weatherStr.length == 0) {return}; 419 | this.statusBar.setText(weatherStr); 420 | } 421 | } else { 422 | this.statusBar.setText(''); 423 | } 424 | } 425 | 426 | // • onunload - Release any resources configured by the plugin • 427 | onunload() { 428 | 429 | } 430 | 431 | // • loadSettings - Load settings from data.json • 432 | async loadSettings() { 433 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 434 | } 435 | 436 | // • saveSettings - Save settings from data.json • 437 | async saveSettings() { 438 | await this.saveData(this.settings); 439 | } 440 | 441 | // • onPick - Callback for pick city suggester • 442 | async onPick(picked: any, settings:any) { 443 | // this.settings = settings; 444 | // console.log('Finally got here!!!!!!', this.settings.location); 445 | 446 | let name = picked.name; 447 | let lat = picked.lat; 448 | let lon = picked.lon; 449 | // this.settings.location = name; 450 | // this.settings.latitude = lat; 451 | // this.settings.longitude = lon; 452 | 453 | // Input event for update to input boxes location, lattitude and longitude 454 | const inputEvent = new InputEvent('input', { 455 | bubbles: true, 456 | cancelable: true, 457 | composed: true, 458 | inputType: 'insertText', 459 | data: 'your_data' 460 | }); 461 | 462 | // Update location, latitude and longitude with the data from selected city 463 | // HACK: Have not been able to get `this` to work from this method so forced to 464 | // update them directly. Need to find a way to fix this - .bind() did not work for me? 465 | let inputEls = document.getElementsByTagName("input"); 466 | for (let idx = 0; idx < inputEls.length; idx++) { 467 | if (inputEls[idx].placeholder == "Enter city Eg. Chicago or South Bend,WA,US") { 468 | inputEls[idx].value = name; 469 | inputEls[idx].dispatchEvent(inputEvent); 470 | }; 471 | if (inputEls[idx].placeholder == "53.5501") { 472 | inputEls[idx].value = lat; 473 | inputEls[idx].dispatchEvent(inputEvent); 474 | }; 475 | if (inputEls[idx].placeholder == "-113.4687") { 476 | inputEls[idx].value = lon; 477 | inputEls[idx].dispatchEvent(inputEvent); 478 | }; 479 | }; 480 | //await this.plugin.saveSettings(); 481 | //await this.plugin.updateWeather(); 482 | //OpenWeather.prototype.saveSettings(); 483 | //OpenWeather.prototype.updateWeather(); 484 | //this.saveSettings(); 485 | //this.saveSettings(); 486 | //this.updateWeather(); 487 | } 488 | } 489 | 490 | // ╭──────────────────────────────────────────────────────────────────────────────╮ 491 | // │ ● Class InsertWeatherModal ● │ 492 | // │ │ 493 | // │ • Insert Weather or Replace Template Strings Modal • │ 494 | // ╰──────────────────────────────────────────────────────────────────────────────╯ 495 | interface Commands { 496 | command: string; 497 | format: string; 498 | } 499 | 500 | let ALL_COMMANDS: { command: string; format: string; }[] = []; 501 | 502 | class InsertWeatherPicker extends SuggestModal implements OpenWeatherSettings{ 503 | location: string; 504 | latitude: string; 505 | longitude: string; 506 | key: string; 507 | units: string; 508 | language: string; 509 | excludeFolder: string; 510 | weatherFormat1: string; 511 | weatherFormat2: string; 512 | weatherFormat3: string; 513 | weatherFormat4: string; 514 | statusbarActive: boolean; 515 | weatherFormatSB: string; 516 | statusbarUpdateFreq: string; 517 | plugin: OpenWeather 518 | command: string; 519 | format: string; 520 | 521 | constructor(location: string, latitude: string, longitude: string, key: string, units: string, language: string, weatherFormat1: string, weatherFormat2: string, weatherFormat3: string, weatherFormat4: string) { 522 | super(app); 523 | this.location = location; 524 | this.latitude = latitude; 525 | this.longitude = longitude; 526 | this.key = key; 527 | this.units = units; 528 | this.language = language; 529 | this.weatherFormat1 = weatherFormat1; 530 | this.weatherFormat2 = weatherFormat2; 531 | this.weatherFormat3 = weatherFormat3; 532 | this.weatherFormat4 = weatherFormat4; 533 | } 534 | 535 | async getSuggestions(query: string): Promise { 536 | ALL_COMMANDS = []; 537 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 538 | if (view?.getViewType() === 'markdown') { 539 | const md = view as MarkdownView; 540 | if (md.getMode() === 'source') { 541 | if (this.weatherFormat1.length > 0) { 542 | let wstr = new FormatWeather (this.location, this.latitude, this.longitude, this.key, this.units, this.language, this.weatherFormat1); 543 | let weatherStr = await wstr.getWeatherString(); 544 | if (weatherStr.length > 0) { 545 | this.weatherFormat1 = weatherStr; 546 | ALL_COMMANDS.push({command: "Insert Weather String - 1", format: this.weatherFormat1}) 547 | } else { 548 | this.weatherFormat1 = ""; 549 | return ALL_COMMANDS.filter((command) => 550 | command.command.toLowerCase().includes(query.toLowerCase()) 551 | ); 552 | } 553 | } 554 | if (this.weatherFormat2.length > 0) { 555 | let wstr = new FormatWeather (this.location, this.latitude, this.longitude, this.key, this.units, this.language, this.weatherFormat2); 556 | let weatherStr = await wstr.getWeatherString(); 557 | if (weatherStr.length > 0) { 558 | this.weatherFormat2 = weatherStr; 559 | ALL_COMMANDS.push({command: "Insert Weather String - 2", format: this.weatherFormat2}) 560 | } else { 561 | this.weatherFormat2 = ""; 562 | return ALL_COMMANDS.filter((command) => 563 | command.command.toLowerCase().includes(query.toLowerCase()) 564 | ); 565 | } 566 | } 567 | if (this.weatherFormat3.length > 0) { 568 | let wstr = new FormatWeather (this.location, this.latitude, this.longitude, this.key, this.units, this.language, this.weatherFormat3); 569 | let weatherStr = await wstr.getWeatherString(); 570 | if (weatherStr.length > 0) { 571 | this.weatherFormat3 = weatherStr; 572 | ALL_COMMANDS.push({command: "Insert Weather String - 3", format: this.weatherFormat3}) 573 | } else { 574 | this.weatherFormat3 = ""; 575 | return ALL_COMMANDS.filter((command) => 576 | command.command.toLowerCase().includes(query.toLowerCase()) 577 | ); 578 | } 579 | } 580 | if (this.weatherFormat4.length > 0) { 581 | let wstr = new FormatWeather (this.location, this.latitude, this.longitude, this.key, this.units, this.language, this.weatherFormat4); 582 | let weatherStr = await wstr.getWeatherString(); 583 | if (weatherStr.length > 0) { 584 | this.weatherFormat4 = weatherStr; 585 | ALL_COMMANDS.push({command: "Insert Weather String - 4", format: this.weatherFormat4}) 586 | } else { 587 | this.weatherFormat4 = ""; 588 | return ALL_COMMANDS.filter((command) => 589 | command.command.toLowerCase().includes(query.toLowerCase()) 590 | ); 591 | } 592 | } 593 | ALL_COMMANDS.push({command: "Replace Template Strings", format: "Replace all occurences of %weather1%, %weather2%, %weather3% and %weather4%\nin the current document."}) 594 | } else { 595 | new Notice("Please enter edit mode to insert weather strings."); 596 | } 597 | } 598 | return ALL_COMMANDS.filter((command) => 599 | command.command.toLowerCase().includes(query.toLowerCase()) 600 | ); 601 | } 602 | 603 | // Renders each suggestion item. 604 | renderSuggestion(command: Commands, el: HTMLElement) { 605 | el.createEl("div", { text: command.command }); 606 | el.createEl("small", { text: command.format }); 607 | } 608 | 609 | // Perform action on the selected suggestion. 610 | async onChooseSuggestion(command: Commands, evt: MouseEvent | KeyboardEvent) { 611 | this.command = command.command 612 | this.format = command.format 613 | this.close(); 614 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 615 | if (!view) return; 616 | const file = app.workspace.getActiveFile(); 617 | let editor = view.getViewData(); 618 | if (editor == null) return; 619 | if (command.command == 'Replace Template Strings') { 620 | if (this.weatherFormat1.length > 0) { 621 | editor = editor.replace(/%weather1%/gmi, this.weatherFormat1); 622 | view.setViewData(editor,false); 623 | file?.vault.modify(file, editor); 624 | } else { 625 | return; 626 | } 627 | if (this.weatherFormat2.length > 0) { 628 | editor = editor.replace(/%weather2%/gmi, this.weatherFormat2); 629 | file?.vault.modify(file, editor); 630 | } else { 631 | return; 632 | } 633 | if (this.weatherFormat3.length > 0) { 634 | editor = editor.replace(/%weather3%/gmi, this.weatherFormat3); 635 | file?.vault.modify(file, editor); 636 | } else { 637 | return; 638 | } 639 | if (this.weatherFormat4.length > 0) { 640 | editor = editor.replace(/%weather4%/gmi, this.weatherFormat4); 641 | file?.vault.modify(file, editor); 642 | } else { 643 | return; 644 | } 645 | } else { 646 | view.editor.replaceSelection(this.format); 647 | } 648 | } 649 | } 650 | 651 | // ╭──────────────────────────────────────────────────────────────────────────────╮ 652 | // │ ● Class CitySearchResultPicker ● │ 653 | // │ │ 654 | // │ • Displays City Search Results in a Suggest Modal • │ 655 | // ╰──────────────────────────────────────────────────────────────────────────────╯ 656 | interface CommandsCity { 657 | city: string; 658 | coords: string; 659 | index: number; 660 | selection: object; 661 | } 662 | 663 | let ALL_CITY_COMMANDS: { city: string; coords: string; index: number; selection: object }[] = []; 664 | 665 | class CitySearchResultPicker extends SuggestModal implements CitySearchResultPicker { 666 | private json: object; 667 | private settings: any; 668 | 669 | constructor(json: object, settings:any) { 670 | super(app); 671 | this.json = json; 672 | this.settings = settings; 673 | } 674 | 675 | async getSuggestions(query: string): Promise { 676 | ALL_CITY_COMMANDS = []; 677 | // { 678 | // "name": "Hell", 679 | // "lat": 42.4347571, 680 | // "lon": -83.9849477, 681 | // "country": "US", 682 | // "state": "Michigan" 683 | // }, 684 | 685 | // Setup choices based on the list of cities returned from the Geocoding API 686 | let cityObjCount = Object.keys(this.json).length; 687 | let values = Object.values(this.json); 688 | for (let i = 0; i < cityObjCount; i++) { 689 | let city = values[i].name; 690 | let state = values[i].state; 691 | if (state == undefined) {state = ""}; 692 | let country = values[i].country; 693 | if (country == undefined) {country = ""}; 694 | let locationStr = city+" "+state+" "+country; 695 | let coords = "Latitude: "+values[i].lat+" "+"Longitude: "+values[i].lon; 696 | ALL_CITY_COMMANDS.push({city: locationStr, coords: coords, index: i, selection: values[i]}); 697 | } 698 | let retry = "None of these match? Try again..."; 699 | let redo = "Select to perform another search"; 700 | ALL_CITY_COMMANDS.push({city: retry, coords: redo, index: 9999, selection: {}}); 701 | return ALL_CITY_COMMANDS.filter((command) => 702 | command.city.toLowerCase().includes(query.toLowerCase()) 703 | ); 704 | } 705 | 706 | // Renders each suggestion item. 707 | renderSuggestion(command: CommandsCity, el: HTMLElement) { 708 | el.createEl("div", { text: command.city }); 709 | el.createEl("small", { text: command.coords }); 710 | } 711 | 712 | // Perform action on the selected suggestion. 713 | onChooseSuggestion(command: CommandsCity, evt: MouseEvent | KeyboardEvent) { 714 | if (command.index == 9999) {return}; // User selected retry 715 | OpenWeather.prototype.onPick(command.selection,this.settings); 716 | } 717 | }; 718 | 719 | 720 | // ╭──────────────────────────────────────────────────────────────────────────────╮ 721 | // │ ● Class OpenWeatherSettingsTab ● │ 722 | // │ │ 723 | // │ • Open Weather Settings Tab • │ 724 | // ╰──────────────────────────────────────────────────────────────────────────────╯ 725 | class OpenWeatherSettingsTab extends PluginSettingTab { 726 | plugin: OpenWeather; 727 | citySearch: string; 728 | cityObj: object; 729 | jsonCities: object; 730 | settings:any 731 | 732 | constructor(app: App, plugin: OpenWeather, settings:any) { 733 | super(app, plugin); 734 | this.plugin = plugin; 735 | this.settings = settings; 736 | } 737 | 738 | display(): void { 739 | const {containerEl} = this; 740 | containerEl.empty(); 741 | let citySearch: string = this.plugin.settings.location; 742 | 743 | // Get vault folders for exclude folder dropdown list 744 | const abstractFiles = app.vault.getAllLoadedFiles(); 745 | const folders: TFolder[] = []; 746 | abstractFiles.forEach((folder: TAbstractFile) => { 747 | if ( 748 | folder instanceof TFolder && folder.name.length > 0 749 | ) { 750 | folders.push(folder); 751 | } 752 | }); 753 | 754 | new Setting(containerEl) 755 | .setName('View on Github') 756 | .addButton(cb => { 757 | cb.setButtonText('GitHub Readme.md'); 758 | cb.setTooltip('Visit OpenWeather Readme.md for detailed plugin information'); 759 | cb.onClick(() => { 760 | window.open(`https://github.com/willasm/obsidian-open-weather/#openweather-plugin-for-obsidian`, '_blank'); 761 | }); 762 | }) 763 | .addButton(cb => { 764 | cb.setButtonText('Example.md'); 765 | cb.setTooltip('A detailed explanation of how I use the plugin in my daily note'); 766 | cb.onClick(() => { 767 | window.open(`https://github.com/willasm/obsidian-open-weather/blob/master/EXAMPLE.md`, '_blank'); 768 | }); 769 | }) 770 | .addButton(cb => { 771 | cb.setButtonText('Report Issue'); 772 | cb.setTooltip('Have any questions or comments? Feel free to post them here'); 773 | cb.onClick(() => { 774 | window.open(`https://github.com/willasm/obsidian-open-weather/issues`, '_blank'); 775 | }); 776 | }) 777 | 778 | // OpenWeatherSettingsTab - H2 Header - OpenWeather API Authentication key (required) 779 | containerEl.createEl('h2', {text: 'OpenWeather API Authentication key (required)'}); 780 | 781 | new Setting(containerEl) 782 | .setName('OpenWeather API key') 783 | .setDesc('A free OpenWeather API key is required for the plugin to work. Go to https://openweathermap.org to register and get a key') 784 | .addText(text => text 785 | .setPlaceholder('Enter OpenWeather API key') 786 | .setValue(this.plugin.settings.key) 787 | .onChange(async (value) => { 788 | this.plugin.settings.key = value; 789 | await this.plugin.saveSettings(); 790 | await this.plugin.updateWeather(); 791 | })); 792 | 793 | // OpenWeatherSettingsTab - H2 Header - Location 794 | containerEl.createEl('h2', {text: 'Location (required)'}); 795 | 796 | new Setting(containerEl) 797 | .setName('Use Geocoding API to get location (recommended)') 798 | .setDesc('The Geocoding API returns the requested locations name, state, country, latitude and longitude') 799 | .addText(text => text 800 | .setPlaceholder('City name to search for') 801 | //.setValue(this.plugin.settings.location) 802 | .onChange(async (value) => { 803 | citySearch = value; 804 | })) 805 | .addButton(cb => { 806 | cb.setButtonText('Get location'); 807 | //cb.setCta(); 808 | cb.onClick(async () => { 809 | let key = this.plugin.settings.key; 810 | //let city = citySearch; 811 | if (key.length == 0) { 812 | new Notice("OpenWeather API key is required"); 813 | } else { 814 | let url; 815 | url = `https://api.openweathermap.org/geo/1.0/direct?q=${citySearch}&limit=5&appid=${key}`; 816 | let req = await fetch(url); 817 | let jsonCitiesObj = await req.json(); 818 | new CitySearchResultPicker(jsonCitiesObj,this.settings).open(); 819 | } 820 | }) 821 | }); 822 | 823 | new Setting(containerEl) 824 | .setName('Enter location') 825 | .setDesc('Name of the city you want to retrieve weather for. Also supports {city name},{country code} or {city name},{state code},{country code} Eg. South Bend,WA,US Please note that searching by states available only for the USA locations') 826 | .addText(text => text 827 | .setPlaceholder('Enter city Eg. Chicago or South Bend,WA,US') 828 | .setValue(this.plugin.settings.location) 829 | .onChange(async (value) => { 830 | this.plugin.settings.location = value; 831 | await this.plugin.saveSettings(); 832 | await this.plugin.updateWeather(); 833 | })); 834 | 835 | new Setting(containerEl) 836 | .setName('Enter latitude') 837 | .setDesc('Please note that API requests by city name have been deprecated although they are still available for use. The preferred method is to use latitude and longitude.') 838 | .addText(text => text 839 | .setPlaceholder('53.5501') 840 | .setValue(this.plugin.settings.latitude) 841 | .onChange(async (value) => { 842 | this.plugin.settings.latitude = value; 843 | await this.plugin.saveSettings(); 844 | await this.plugin.updateWeather(); 845 | })); 846 | 847 | new Setting(containerEl) 848 | .setName('Enter longitude') 849 | .setDesc('Please note that API requests by city name have been deprecated although they are still available for use. The preferred method is to use latitude and longitude.') 850 | .addText(text => text 851 | .setPlaceholder('-113.4687') 852 | .setValue(this.plugin.settings.longitude) 853 | .onChange(async (value) => { 854 | this.plugin.settings.longitude = value; 855 | await this.plugin.saveSettings(); 856 | await this.plugin.updateWeather(); 857 | })); 858 | 859 | // OpenWeatherSettingsTab - H2 Header - General preferences 860 | containerEl.createEl('h2', {text: 'General preferences'}); 861 | 862 | new Setting(containerEl) 863 | .setName("Units of measurement") 864 | .setDesc("Units of measurement. Metric, Imperial and Standard units are available") 865 | .addDropdown(dropDown => { 866 | dropDown.addOption('metric', 'Metric'); 867 | dropDown.addOption('imperial', 'Imperial'); 868 | dropDown.addOption('standard', 'Standard'); 869 | dropDown.onChange(async (value) => { 870 | this.plugin.settings.units = value; 871 | await this.plugin.saveSettings(); 872 | await this.plugin.updateWeather(); 873 | }) 874 | .setValue(this.plugin.settings.units); 875 | }); 876 | 877 | new Setting(containerEl) 878 | .setName("Language") 879 | .setDesc("Supported languages available Note: This only affects text returned from the OpenWeather API") 880 | .addDropdown(dropDown => { 881 | dropDown.addOption('af', 'Afrikaans'); 882 | dropDown.addOption('al', 'Albanian'); 883 | dropDown.addOption('ar', 'Arabic'); 884 | dropDown.addOption('az', 'Azerbaijani'); 885 | dropDown.addOption('bg', 'Bulgarian'); 886 | dropDown.addOption('ca', 'Catalan'); 887 | dropDown.addOption('cz', 'Czech'); 888 | dropDown.addOption('da', 'Danish'); 889 | dropDown.addOption('de', 'German'); 890 | dropDown.addOption('el', 'Greek'); 891 | dropDown.addOption('en', 'English'); 892 | dropDown.addOption('eu', 'Basque'); 893 | dropDown.addOption('fa', 'Persian (Farsi)'); 894 | dropDown.addOption('fi', 'Finnish'); 895 | dropDown.addOption('fr', 'French'); 896 | dropDown.addOption('gl', 'Galician'); 897 | dropDown.addOption('he', 'Hebrew'); 898 | dropDown.addOption('hi', 'Hindi'); 899 | dropDown.addOption('hr', 'Croatian'); 900 | dropDown.addOption('hu', 'Hungarian'); 901 | dropDown.addOption('id', 'Indonesian'); 902 | dropDown.addOption('it', 'Italian'); 903 | dropDown.addOption('ja', 'Japanese'); 904 | dropDown.addOption('kr', 'Korean'); 905 | dropDown.addOption('la', 'Latvian'); 906 | dropDown.addOption('lt', 'Lithuanian'); 907 | dropDown.addOption('mk', 'Macedonian'); 908 | dropDown.addOption('no', 'Norwegian'); 909 | dropDown.addOption('nl', 'Dutch'); 910 | dropDown.addOption('pl', 'Polish'); 911 | dropDown.addOption('pt', 'Portuguese'); 912 | dropDown.addOption('pt_br', 'Português Brasil'); 913 | dropDown.addOption('ro', 'Romanian'); 914 | dropDown.addOption('ru', 'Russian'); 915 | dropDown.addOption('sv', 'Swedish'); 916 | dropDown.addOption('sk', 'Slovak'); 917 | dropDown.addOption('sl', 'Slovenian'); 918 | dropDown.addOption('sp', 'Spanish'); 919 | dropDown.addOption('sr', 'Serbian'); 920 | dropDown.addOption('th', 'Thai'); 921 | dropDown.addOption('tr', 'Turkish'); 922 | dropDown.addOption('ua', 'Ukrainian'); 923 | dropDown.addOption('vi', 'Vietnamese'); 924 | dropDown.addOption('zh_cn', 'Chinese Simplified'); 925 | dropDown.addOption('zh_tw', 'Chinese Traditional'); 926 | dropDown.addOption('zu', 'Zulu'); 927 | dropDown.onChange(async (value) => { 928 | this.plugin.settings.language = value; 929 | await this.plugin.saveSettings(); 930 | await this.plugin.updateWeather(); 931 | }) 932 | .setValue(this.plugin.settings.language); 933 | }); 934 | 935 | // OpenWeatherSettingsTab - H2 Header - Exclude Folder (Exclude Folder for Template String Replacement) 936 | containerEl.createEl('h2', {text: 'Folder to exclude from automatic template strings replacement'}); 937 | 938 | new Setting(containerEl) 939 | .setName("Exclude folder") 940 | .setDesc("Folder to exclude from automatic template string replacement") 941 | .addDropdown(dropDown => { 942 | folders.forEach(e => { 943 | dropDown.addOption(e.name,e.name); 944 | }); 945 | dropDown.onChange(async (value) => { 946 | this.plugin.settings.excludeFolder = value; 947 | await this.plugin.saveSettings(); 948 | }) 949 | .setValue(this.plugin.settings.excludeFolder); 950 | }); 951 | 952 | // OpenWeatherSettingsTab - H2 Header - Weather Strings Formatting (4 Strings are Available) 953 | containerEl.createEl('h2', {text: 'Weather strings formatting (4 strings are available)'}); 954 | 955 | new Setting(containerEl) 956 | .setName("Weather string 1") 957 | .setDesc("Feel free to change this to whatever you like. See the README.md on Github for all available macros.") 958 | .addTextArea((textArea: TextAreaComponent) => { 959 | textArea 960 | .setPlaceholder('Weather string 1') 961 | .setValue(this.plugin.settings.weatherFormat1) 962 | .onChange(async (value) => { 963 | this.plugin.settings.weatherFormat1 = value; 964 | await this.plugin.saveSettings(); 965 | }) 966 | textArea.inputEl.setAttr("rows", 10); 967 | textArea.inputEl.setAttr("cols", 60); 968 | }); 969 | 970 | new Setting(containerEl) 971 | .setName("Weather string 2") 972 | .setDesc("Feel free to change this to whatever you like. See the README.md on Github for all available macros.") 973 | .addTextArea((textArea: TextAreaComponent) => { 974 | textArea 975 | .setPlaceholder('Weather string 2') 976 | .setValue(this.plugin.settings.weatherFormat2) 977 | .onChange(async (value) => { 978 | this.plugin.settings.weatherFormat2 = value; 979 | await this.plugin.saveSettings(); 980 | }) 981 | textArea.inputEl.setAttr("rows", 10); 982 | textArea.inputEl.setAttr("cols", 60); 983 | }); 984 | 985 | new Setting(containerEl) 986 | .setName("Weather string 3") 987 | .setDesc("Feel free to change this to whatever you like. See the README.md on Github for all available macros.") 988 | .addTextArea((textArea: TextAreaComponent) => { 989 | textArea 990 | .setPlaceholder('Weather string 3') 991 | .setValue(this.plugin.settings.weatherFormat3) 992 | .onChange(async (value) => { 993 | this.plugin.settings.weatherFormat3 = value; 994 | await this.plugin.saveSettings(); 995 | }) 996 | textArea.inputEl.setAttr("rows", 10); 997 | textArea.inputEl.setAttr("cols", 60); 998 | }); 999 | 1000 | new Setting(containerEl) 1001 | .setName("Weather string 4") 1002 | .setDesc("Feel free to change this to whatever you like. See the README.md on Github for all available macros.") 1003 | .addTextArea((textArea: TextAreaComponent) => { 1004 | textArea 1005 | .setPlaceholder('Weather string 4') 1006 | .setValue(this.plugin.settings.weatherFormat4) 1007 | .onChange(async (value) => { 1008 | this.plugin.settings.weatherFormat4 = value; 1009 | await this.plugin.saveSettings(); 1010 | }) 1011 | textArea.inputEl.setAttr("rows", 10); 1012 | textArea.inputEl.setAttr("cols", 60); 1013 | }); 1014 | 1015 | // OpenWeatherSettingsTab - H2 Header - Show Weather in Statusbar Options 1016 | if (Platform.isDesktop) { 1017 | containerEl.createEl('h2', {text: 'Show weather in statusbar options'}); 1018 | 1019 | new Setting(containerEl) 1020 | .setName('Show weather in statusbar') 1021 | .setDesc('Enable weather display in statusbar') 1022 | .addToggle(toggle => toggle 1023 | .setValue(this.plugin.settings.statusbarActive) 1024 | .onChange(async (value) => { 1025 | this.plugin.settings.statusbarActive = value; 1026 | await this.plugin.saveSettings(); 1027 | await this.plugin.updateWeather(); 1028 | })); 1029 | 1030 | new Setting(containerEl) 1031 | .setName("Weather string format statusbar") 1032 | .setDesc("Weather string format for the statusbar") 1033 | .addTextArea((textArea: TextAreaComponent) => { 1034 | textArea 1035 | .setPlaceholder('Statusbar Weather Format') 1036 | .setValue(this.plugin.settings.weatherFormatSB) 1037 | .onChange(async (value) => { 1038 | this.plugin.settings.weatherFormatSB = value; 1039 | await this.plugin.saveSettings(); 1040 | await this.plugin.updateWeather(); 1041 | }) 1042 | textArea.inputEl.setAttr("rows", 10); 1043 | textArea.inputEl.setAttr("cols", 60); 1044 | }); 1045 | } else { 1046 | this.plugin.settings.statusbarActive = false; // Set statusbar inactive for mobile 1047 | } 1048 | 1049 | // OpenWeatherSettingsTab - H2 Header - Show Weather in Statusbar and Dynamic DIV's update frequency 1050 | containerEl.createEl('h2', {text: `Weather update frequency`}); 1051 | 1052 | new Setting(containerEl) 1053 | .setName("Update frequency") 1054 | .setDesc("Update frequency for weather information displayed on the statusbar and in dynamic DIV's") 1055 | .addDropdown(dropDown => { 1056 | dropDown.addOption('1', 'Every Minute'); 1057 | dropDown.addOption('5', 'Every 5 Minutes'); 1058 | dropDown.addOption('10', 'Every 10 Minutes'); 1059 | dropDown.addOption('15', 'Every 15 Minutes'); 1060 | dropDown.addOption('20', 'Every 20 Minutes'); 1061 | dropDown.addOption('30', 'Every 30 Minutes'); 1062 | dropDown.addOption('60', 'Every Hour'); 1063 | dropDown.onChange(async (value) => { 1064 | this.plugin.settings.statusbarUpdateFreq = value; 1065 | await this.plugin.saveSettings(); 1066 | await this.plugin.updateWeather(); 1067 | }) 1068 | .setValue(this.plugin.settings.statusbarUpdateFreq) 1069 | }); 1070 | } 1071 | }; 1072 | 1073 | // Weather Placeholders 1074 | // ==================== 1075 | // weather.description %desc% 1076 | // weather.icon %icon% 1077 | // main.temp %temp% 1078 | // main.feels_like %feels% 1079 | // main.temp_min %tempmin% 1080 | // main.temp_max %tempmax% 1081 | // main.pressure %pressure% 1082 | // main.humidity %humidity% 1083 | // main.sea_level %pressure-sl% 1084 | // main.grnd_level %pressure-gl% 1085 | // visibility %visibility% 1086 | // wind.speed %wind-speed% 1087 | // wind.speedms %wind-speed-ms% 1088 | // wind.deg %wind-deg% 1089 | // wind.gust %wind-gust% 1090 | // dt (date time) %date% %time% 1091 | // sys.sunrise %sunrise% 1092 | // sys.sunset %sunset% 1093 | 1094 | // Example of API response 1095 | // ======================= 1096 | // 1097 | // { 1098 | // "coord": { 1099 | // "lon": 10.99, 1100 | // "lat": 44.34 1101 | // }, 1102 | // "weather": [ 1103 | // { 1104 | // "id": 501, 1105 | // "main": "Rain", 1106 | // "description": "moderate rain", 1107 | // "icon": "10d" 1108 | // } 1109 | // ], 1110 | // "base": "stations", 1111 | // "main": { 1112 | // "temp": 298.48, 1113 | // "feels_like": 298.74, 1114 | // "temp_min": 297.56, 1115 | // "temp_max": 300.05, 1116 | // "pressure": 1015, 1117 | // "humidity": 64, 1118 | // "sea_level": 1015, 1119 | // "grnd_level": 933 1120 | // }, 1121 | // "visibility": 10000, 1122 | // "wind": { 1123 | // "speed": 0.62, 1124 | // "deg": 349, 1125 | // "gust": 1.18 1126 | // }, 1127 | // "rain": { 1128 | // "1h": 3.16 1129 | // }, 1130 | // "clouds": { 1131 | // "all": 100 1132 | // }, 1133 | // "dt": 1661870592, 1134 | // "sys": { 1135 | // "type": 2, 1136 | // "id": 2075663, 1137 | // "country": "IT", 1138 | // "sunrise": 1661834187, 1139 | // "sunset": 1661882248 1140 | // }, 1141 | // "timezone": 7200, 1142 | // "id": 3163858, 1143 | // "name": "Zocca", 1144 | // "cod": 200 1145 | // } 1146 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "open-weather", 3 | "name": "OpenWeather", 4 | "version": "1.8.1", 5 | "minAppVersion": "1.4.10", 6 | "description": "This plugin returns the current weather from OpenWeather in a configurable string format.", 7 | "author": "willasm", 8 | "authorUrl": "https://github.com/willasm", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-open-weather", 3 | "version": "1.8.1", 4 | "description": "Returns weather information from OpenWeather API", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [ 12 | "weather", 13 | "plugin" 14 | ], 15 | "author": "willasm", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@types/node": "^16.11.6", 19 | "@typescript-eslint/eslint-plugin": "5.29.0", 20 | "@typescript-eslint/parser": "5.29.0", 21 | "builtin-modules": "3.3.0", 22 | "esbuild": "0.14.47", 23 | "obsidian": "latest", 24 | "tslib": "2.4.0", 25 | "typescript": "4.7.4" 26 | }, 27 | "dependencies": { 28 | "date-fns": "^3.3.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0", 3 | "1.1.0": "0.15.0", 4 | "1.2.0": "0.15.0", 5 | "1.3.0": "0.15.0", 6 | "1.4.0": "0.15.0", 7 | "1.5.0": "0.15.0", 8 | "1.5.1": "0.15.0", 9 | "1.6.0": "0.15.0", 10 | "1.6.1": "1.4.10", 11 | "1.7.0": "1.4.10", 12 | "1.7.1": "1.4.10", 13 | "1.8.0": "1.4.10", 14 | "1.8.1": "1.4.10" 15 | } 16 | --------------------------------------------------------------------------------