├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── edit-entry.md │ └── new-entry.md ├── .gitignore ├── README.md ├── apache ├── blog.gui.service └── blog.ryanrickgauer.com.conf ├── entries ├── 2024-nflpa-team-report-cards.md ├── MyPlanner-Database-Tables.md ├── a-random-walk-down-wall-street.md ├── apache-proxies.md ├── asp.net-basic-auth-example.md ├── asp.net-controller-example.md ├── auto-table.md ├── bootstrap-notes.md ├── c++-standard-library-sequential-containers.md ├── common-functions-language-table.md ├── courseroster.com.md ├── cpp-classes.md ├── cpp-enums.md ├── csharp-dialogs.md ├── css-color-names.md ├── css-shadows-snippet.md ├── daily-omg-facts.md ├── dotnet-custom-attribute-methods.md ├── dotnet-interop-fundamentals.md ├── favorite-blogs.md ├── first-committ-at-new-job.md ├── first-contribution.md ├── first-custom-built-pc.md ├── flask-jinja-macros.md ├── flask-request-url-properties.md ├── git-commands.md ├── github-stars.md ├── goals-for-2020.md ├── hashing-passwords-in-php.md ├── html-inputs.md ├── http-status-codes.md ├── icu-guide.md ├── installing-babel.md ├── javascript-classes.md ├── javascript-fetch-api.md ├── javascript-notes.md ├── javascript-tablesort-example.md ├── jquery-contains.md ├── markdown-cheatsheet.md ├── markdown-code-block-languages.md ├── masm-instruction-set-table.md ├── matplotlib-notes.md ├── mime-types.md ├── mysql-create-stored-procedure.md ├── mysql-notes.md ├── mysql-stored-functions.md ├── mysqldump-notes.md ├── number-conversions.md ├── operating-systems-exam-1-review.md ├── personal-python-functions.md ├── php-bootstrap-alert-function.md ├── php-classes.md ├── php-notes.md ├── pin-batch-files-to-taskbar.md ├── postgres-notes.md ├── prism-examples.md ├── python-bs4.md ├── python-classes.md ├── python-dataclass-serializer.md ├── python-file-operations.md ├── python-json-operations.md ├── python-mysql-notes.md ├── python-resources.md ├── python-string-functions.md ├── regex-cheatsheet.md ├── rollup.js-configuration-file.md ├── searching-a-table-with-javascript.md ├── software-development-tools.md ├── sorting-algorithms.md ├── sorting-js-array.md ├── sql-format.md ├── sublime-text-keyboard-shortcuts.md ├── test-delete.md ├── tips-for-undergrads-starting-their-cs-program.md ├── web-application-template.md ├── web-development-resources.md └── word-search-generator-project.md ├── scripts ├── dev │ ├── start-css.bat │ ├── start-dev.bat │ ├── start-flask-server.bat │ └── tables.bat └── vps │ ├── deploy.sh │ └── view-log.sh ├── sql ├── tables │ ├── Entries.sql │ ├── Topics.sql │ └── Users.sql └── views │ ├── View_Entries.sql │ └── View_Used_Topics.sql ├── src └── gui │ └── Blog │ ├── Blog.Gui │ ├── Blog.Gui.csproj │ ├── Controllers │ │ └── HomeController.cs │ ├── Models │ │ ├── EntriesViewModel.cs │ │ ├── EntryViewModel.cs │ │ └── ErrorViewModel.cs │ ├── Other │ │ └── WebGuiDependencyService.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Views │ │ ├── Home │ │ │ ├── Entry.cshtml │ │ │ └── Index.cshtml │ │ ├── Includes │ │ │ ├── _Copyright.cshtml │ │ │ ├── _Footer.cshtml │ │ │ └── _Header.cshtml │ │ ├── Shared │ │ │ └── Error.cshtml │ │ └── _ViewImports.cshtml │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── custom │ │ │ └── style.scss │ │ ├── favicon.ico │ │ ├── img │ │ └── icon.png │ │ ├── js │ │ └── custom │ │ │ ├── auto-tables.js │ │ │ ├── entry.js │ │ │ └── home.js │ │ └── lib │ │ ├── css │ │ ├── github.css │ │ ├── hamburgers.css │ │ └── prism.css │ │ └── js │ │ ├── prism.js │ │ ├── tablesearch.js │ │ ├── tablesort │ │ ├── tablesort.date.js │ │ ├── tablesort.dotstep.js │ │ ├── tablesort.js │ │ └── tablesort.number.js │ │ ├── typeit.min.js │ │ └── typeit.modern.min.js │ ├── Blog.Service │ ├── Blog.Service.csproj │ ├── Domain │ │ ├── Configs │ │ │ ├── ConfigurationDev.cs │ │ │ └── IConfigs.cs │ │ ├── Contracts │ │ │ ├── IModelForm.cs │ │ │ └── ITableView.cs │ │ ├── CustomAttributes │ │ │ ├── AutoCopyPropertyAttribute.cs │ │ │ ├── CopyToPropertyAttribute.cs │ │ │ └── SqlColumnAttribute.cs │ │ ├── Model │ │ │ ├── Entry.cs │ │ │ └── EntryTopic.cs │ │ ├── Other │ │ │ ├── ClassPropertyAttributes.cs │ │ │ ├── InsertAutoRowResult.cs │ │ │ └── PropertyAttribute.cs │ │ └── TableView │ │ │ ├── EntryTableView.cs │ │ │ └── TopicTableView.cs │ ├── Mappers │ │ └── Tables │ │ │ ├── EntryTableViewMapper.cs │ │ │ ├── TableMapper.cs │ │ │ └── TopicTableViewMapper.cs │ ├── Repository │ │ ├── Commands │ │ │ ├── EntryCommands.cs │ │ │ └── TopicCommands.cs │ │ ├── Contracts │ │ │ ├── IEntryRepository.cs │ │ │ └── ITopicRepository.cs │ │ ├── Implementations │ │ │ ├── EntryRepository.cs │ │ │ └── TopicRepository.cs │ │ └── Other │ │ │ ├── DatabaseConnection.cs │ │ │ └── RepositoryUtils.cs │ └── Services │ │ ├── Contracts │ │ ├── IEntryService.cs │ │ ├── IMarkdownService.cs │ │ ├── ITableMapperService.cs │ │ └── ITopicService.cs │ │ └── Implementations │ │ ├── DependencyService.cs │ │ ├── EntryService.cs │ │ ├── MarkdownService.cs │ │ ├── TableMapperService.cs │ │ └── TopicService.cs │ ├── Blog.WpfGui │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Assets │ │ ├── wpfui-icon-1024.png │ │ └── wpfui-icon-256.png │ ├── Blog.WpfGui.csproj │ ├── Converters │ │ ├── BoolToVisibilityConverter.cs │ │ ├── BoolToVisibilityInverseConverter.cs │ │ └── EnumToBooleanConverter.cs │ ├── Helpers │ │ ├── BindingProxy.cs │ │ └── IMessengerHandler.cs │ ├── Messenger │ │ └── ViewMessages.cs │ ├── Models │ │ ├── AppConfig.cs │ │ └── DataColor.cs │ ├── Resources │ │ └── Translations.cs │ ├── Services │ │ ├── ApplicationHostService.cs │ │ └── PageService.cs │ ├── Usings.cs │ ├── ViewModels │ │ ├── Base │ │ │ └── ViewModel.cs │ │ ├── Pages │ │ │ ├── DataViewModel.cs │ │ │ ├── EntriesViewModel.cs │ │ │ ├── EntryFormViewModel.cs │ │ │ ├── LandingViewModel.cs │ │ │ ├── SettingsViewModel.cs │ │ │ ├── TopicFormViewModel.cs │ │ │ └── TopicsViewModel.cs │ │ └── Windows │ │ │ └── MainWindowViewModel.cs │ ├── Views │ │ ├── Pages │ │ │ ├── DataPage.xaml │ │ │ ├── DataPage.xaml.cs │ │ │ ├── EntriesPage.xaml │ │ │ ├── EntriesPage.xaml.cs │ │ │ ├── EntryFormPage.xaml │ │ │ ├── EntryFormPage.xaml.cs │ │ │ ├── LandingPage.xaml │ │ │ ├── LandingPage.xaml.cs │ │ │ ├── SettingsPage.xaml │ │ │ ├── SettingsPage.xaml.cs │ │ │ ├── TopicFormPage.xaml │ │ │ ├── TopicFormPage.xaml.cs │ │ │ ├── TopicsPage.xaml │ │ │ └── TopicsPage.xaml.cs │ │ ├── UserControls │ │ │ ├── PageTitleControl.xaml │ │ │ └── PageTitleControl.xaml.cs │ │ └── Windows │ │ │ ├── MainWindow.xaml │ │ │ └── MainWindow.xaml.cs │ └── wpfui-icon.ico │ └── Blog.sln └── topics-list └── topics.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | *.php linguist-language=PHP 2 | *.sql linguist-language=sql 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/edit-entry.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Edit Entry 3 | about: Template for editing an existing blog post 4 | title: "[Entry_Title]: SUMMARY_OF_ISSUE" 5 | labels: edit entry 6 | assignees: rrickgauer 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-entry.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Entry 3 | about: Template for creating a new entry 4 | title: "[Entry_Title]: Summary_Of_Issue" 5 | labels: new entry 6 | assignees: rrickgauer 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | 3 | https://blog.ryanrickgauer.com/ 4 | -------------------------------------------------------------------------------- /apache/blog.gui.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=blog.ryanrickgauer.com 3 | 4 | [Service] 5 | WorkingDirectory=/var/www/blog/src/gui/Blog/Blog.Gui/bin/Release/net8.0/linux-x64 6 | #WorkingDirectory=var/www/blog/src/gui/Blog/Blog.Gui 7 | ExecStart=/usr/bin/dotnet /var/www/blog/src/gui/Blog/Blog.Gui/bin/Release/net8.0/linux-x64/Blog.Gui.dll 8 | Restart=always 9 | # Restart service after 10 seconds if the dotnet service crashes: 10 | RestartSec=10 11 | KillSignal=SIGINT 12 | SyslogIdentifier=blog-log 13 | User=www-data 14 | Environment=ASPNETCORE_ENVIRONMENT=Production 15 | Environment=ASPNETCORE_URLS=http://blog.ryanrickgauer.com:5050/ 16 | 17 | [Install] 18 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /apache/blog.ryanrickgauer.com.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerName blog.ryanrickgauer.com 3 | ProxyPass / http://blog.ryanrickgauer.com:5050/ 4 | ProxyPassReverse / http://blog.ryanrickgauer.com:5050/ 5 | RequestHeader set X-Forwarded-Port 80 6 | 7 | RewriteEngine on 8 | RewriteCond %{SERVER_NAME} =www.blog.ryanrickgauer.com [OR] 9 | RewriteCond %{SERVER_NAME} =blog.ryanrickgauer.com 10 | RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] 11 | 12 | ErrorLog ${APACHE_LOG_DIR}/blog.ryanrickgauer.com-error.log 13 | 14 | 15 | 16 | 17 | 18 | ServerName blog.ryanrickgauer.com 19 | 20 | SSLEngine On 21 | SSLCertificateFile /etc/letsencrypt/live/blog.ryanrickgauer.com/fullchain.pem 22 | SSLCertificateKeyFile /etc/letsencrypt/live/blog.ryanrickgauer.com/privkey.pem 23 | 24 | ProxyPass / http://blog.ryanrickgauer.com:5050/ 25 | ProxyPassReverse / http://blog.ryanrickgauer.com:5050/ 26 | ProxyPreserveHost On 27 | 28 | RequestHeader set X-Forwarded-Port 443 29 | RequestHeader set X-Forwarded-Scheme https 30 | 31 | -------------------------------------------------------------------------------- /entries/apache-proxies.md: -------------------------------------------------------------------------------- 1 | # Proxying HTTP Requests with Apache 2 | 3 | This was a note I made for myself when I was deploying [wmiys.com](https://wmiys.com/login) to a VPS. 4 | 5 | ## Background 6 | 7 | For mod_wsgi-express, you can only have 1 application listening on the same port. However, I needed both the api and the gui to both be listening on the same ports: 80 and 443. Additionally, I wanted them both to use HTTPS. 8 | 9 | To resolve this, I needed to setup proxies using Apache. 10 | 11 | Essentially, I needed apache to listen on ports 80/443 for each domain requests: **api.wmiys.com** and **wmiys.com**. Then, take those requests and rewrite the urls to use their ports that they are listening on with mod_wsgi. 12 | 13 | How it works is basically Apache is listening for these urls at the top of the server. Once it receives one of those url requests, it takes it and appends the port to the url that mod_wsgi is listening for. Then, it sends the requests to mod_wsgi. *I think... This is probably completely wrong, but this is how I could make myself understand proxies.* 14 | 15 | ## Settings for mod_wsgi-express 16 | 17 | Since every mod_wsgi application needs its own port to listen on, I needed to define different port numbers for both the api and the front-end applications. I think of them like private/internal ports that only the server knows about. The outside world / incoming website requests only know about ports 80/443. 18 | 19 | I programmed the API to internally listen on port 81, and the gui to listen on 82. Now when a user goes to wmiys.com, Apache will secretly send that request to port 82. 20 | 21 | 22 | ## Apache Conf Files 23 | 24 | Each application needed its own Apache configuration file and placed in `/etc/apache2/sites-available`: 25 | 26 | ### api.wmiys.com.conf 27 | 28 | ```apacheconf 29 | 30 | ServerName api.wmiys.com 31 | ProxyPass / http://api.wmiys.com:81/ 32 | 33 | RewriteEngine On 34 | RewriteRule .* - [E=SERVER_PORT:%{SERVER_PORT},NE] 35 | 36 | RequestHeader set X-Forwarded-Port %{SERVER_PORT}e 37 | 38 | 39 | 40 | ServerName api.wmiys.com 41 | 42 | SSLEngine On 43 | SSLCertificateFile /etc/letsencrypt/live/api.wmiys.com/fullchain.pem 44 | SSLCertificateKeyFile /etc/letsencrypt/live/api.wmiys.com/privkey.pem 45 | 46 | ProxyPass / http://api.wmiys.com:81/ 47 | 48 | RequestHeader set X-Forwarded-Port 443 49 | RequestHeader set X-Forwarded-Scheme https 50 | 51 | ``` 52 | 53 | ### wmiys.com.conf 54 | 55 | ```apacheconf 56 | 57 | ServerName wmiys.com 58 | ProxyPass / http://wmiys.com:82/ 59 | 60 | RewriteEngine On 61 | RewriteRule .* - [E=SERVER_PORT:%{SERVER_PORT},NE] 62 | 63 | RequestHeader set X-Forwarded-Port %{SERVER_PORT}e 64 | 65 | 66 | 67 | ServerName wmiys.com 68 | 69 | SSLEngine On 70 | SSLCertificateFile /etc/letsencrypt/live/wmiys.com/fullchain.pem 71 | SSLCertificateKeyFile /etc/letsencrypt/live/wmiys.com/privkey.pem 72 | 73 | ProxyPass / http://wmiys.com:82/ 74 | 75 | RequestHeader set X-Forwarded-Port 443 76 | RequestHeader set X-Forwarded-Scheme https 77 | 78 | ``` 79 | 80 | ## Further reading 81 | 82 | http://blog.dscpl.com.au/2015/06/proxying-to-python-web-application.html 83 | -------------------------------------------------------------------------------- /entries/asp.net-controller-example.md: -------------------------------------------------------------------------------- 1 | This is a sample of a simple controller for asp.net: 2 | 3 | ```csharp 4 | using Microsoft.AspNetCore.Mvc; 5 | using System.Collections.Generic; 6 | 7 | namespace WebApp1.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ValuesController : ControllerBase 12 | { 13 | // GET api/values 14 | [HttpGet] 15 | public ActionResult> Get() 16 | { 17 | return new string[] { "value1", "value2" }; 18 | } 19 | 20 | // GET api/values/5 21 | [HttpGet("{id}")] 22 | public ActionResult Get(int id) 23 | { 24 | return "value"; 25 | } 26 | 27 | // POST api/values 28 | [HttpPost] 29 | public void Post([FromBody] string value) 30 | { 31 | } 32 | 33 | // PUT api/values/5 34 | [HttpPut("{id}")] 35 | public void Put(int id, [FromBody] string value) 36 | { 37 | } 38 | 39 | // DELETE api/values/5 40 | [HttpDelete("{id}")] 41 | public void Delete(int id) 42 | { 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | [Source](https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/conventions?view=aspnetcore-6.0#create-web-api-conventions) 49 | -------------------------------------------------------------------------------- /entries/auto-table.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a demo of my [auto-tables javascript library](https://github.com/rrickgauer/auto-tables) 4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
IndexFirst nameLast nameEmailCountryDob
1DreddyNorgatednorgate0@java.comIndonesia03/09/1956
2RodgerParkinsrparkins1@imdb.comBrazil02/25/1881
3UdellTreebyutreeby2@xing.comBolivia01/25/1969
4MarsiellaGhelarduccimghelarducci3@cpanel.netJapan10/16/1976
5KerwinnDickeykdickey4@cornell.eduThailand10/04/1982
6RaniMeatyardrmeatyard5@twitpic.comChina09/10/1898
7BiankaSpellacybspellacy6@icq.comUnited States08/28/1912
8DiannaJouanetondjouaneton7@samsung.comMontenegro12/04/1915
9EmlynPreatorepreator8@chicagotribune.comIndonesia03/13/1954
10KellieLeipeltkleipelt9@columbia.eduChina12/03/1976
11TrishaGiorgionitgiorgionia@cloudflare.comBosnia and Herzegovina07/30/1961
12LynGaultlgaultb@google.esPortugal03/20/1971
13CarolanRedgravecredgravec@twitpic.comArgentina09/12/1931
14AdlerFassetafassetd@sohu.comAlbania05/15/1961
15LucretiaHuxtonlhuxtone@pen.ioColombia11/17/1896
36 |
37 | 38 | 39 |
40 | Click me to view code 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /entries/common-functions-language-table.md: -------------------------------------------------------------------------------- 1 | ## Content 2 | 3 | 1. [Background](#background) 4 | 2. [Function Table](#function-table) 5 | 3. [For Loops](#for-loops) 6 | 7 | ## [Background](#content) 8 | 9 | This is going to be an ongoing project where that reviews some functions/code snippets and the different syntax between common programming languages. 10 | 11 | ## [Function Table](#content) 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Functions:PHPJavaScriptPython
String lengthstrlen($str)str.lengthlen(str)
Array sizecount($array)array.lengthlen(array)
Creating a functionfunction print()function print()def function:
47 | 48 | 49 | ## [For Loops](#content) 50 | 51 | ### PHP 52 | 53 | ```php 54 | for ($x = 0; $x <= 100; $x+=10) { 55 | echo "The number is: $x"; 56 | } 57 | ``` 58 | 59 | ### JavaScript 60 | 61 | ```javascript 62 | for (i = 0; i < len; i++) { 63 | text += cars[i]; 64 | } 65 | ``` 66 | 67 | ### Python 68 | 69 | #### Looping through a list 70 | 71 | ```python 72 | fruits = ["apple", "banana", "cherry"] 73 | for x in fruits: 74 | print(x) 75 | ``` 76 | 77 | #### Looping through string index 78 | 79 | ```python 80 | for x in "banana": 81 | print(x) 82 | ``` 83 | 84 | #### Looping through a range 85 | 86 | ```python 87 | for x in range(6): 88 | print(x) 89 | ``` 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /entries/courseroster.com.md: -------------------------------------------------------------------------------- 1 | # Course Roster 2 | 3 | I finally got to updating my site [courseroster.com](https://courseroster.com/login.php). It's a site where [NIU](https://www.niu.edu/index.shtml) students can go see what courses their friends signed up for. I wrote the first version of this about a year ago, and I even bought the domain name. However, when I started pledging my fraternity and school started picking up, I kind of forgot about it. Now I've totally revamped it and I can't wait to show my friends and hear any comments they may have about it. 4 | 5 | If you have any ideas, comments, or questions, feel free to shoot me an email at rrickgauer1@gmail.com! Thanks and enjoy. 6 | -------------------------------------------------------------------------------- /entries/cpp-classes.md: -------------------------------------------------------------------------------- 1 | 2 | The purpose of this post is to review some basic C++ class concepts. 3 | 4 | ## Basic Classes 5 | 6 | This example will create a class named `Person` that will check if someone is old enough to drive. 7 | 8 | The class *declaration* will go into `Person.h`: 9 | 10 | ```cpp 11 | #pragma once 12 | 13 | #include 14 | 15 | class Person 16 | { 17 | public: 18 | // symbolic constants 19 | static const short MINIMUM_DRIVER_AGE = 16; 20 | 21 | // typedef for common argument types that are prefixed with const and are references 22 | typedef const std::string& argstring; 23 | typedef const short& argshort; 24 | 25 | // Constructor with initializer list 26 | Person(argstring nameFirst, argstring nameLast, argshort age) : nameFirst(nameFirst), nameLast(nameLast), age(age) {}; 27 | 28 | bool ableToDrive(); 29 | 30 | // Getters 31 | std::string getNameFirst() const; 32 | std::string getNameLast() const; 33 | short getAge() const; 34 | 35 | private: 36 | std::string nameFirst; 37 | std::string nameLast; 38 | short age; 39 | }; 40 | 41 | 42 | ``` 43 | 44 | Then, the class *definition* will go into `Person.cpp`: 45 | 46 | ```cpp 47 | #include "Person.h" 48 | 49 | /// 50 | /// Checks if person is able to drive a car (is age >= 16) 51 | /// 52 | /// 53 | bool Person::ableToDrive() 54 | { 55 | return age < Person::MINIMUM_DRIVER_AGE; 56 | } 57 | 58 | /// 59 | /// Return the object's nameFirst value 60 | /// 61 | /// 62 | std::string Person::getNameFirst() const 63 | { 64 | return nameFirst; 65 | } 66 | 67 | /// 68 | /// Return the object's nameLast value 69 | /// 70 | /// 71 | std::string Person::getNameLast() const 72 | { 73 | return nameLast; 74 | } 75 | 76 | /// 77 | /// Return the object's age value 78 | /// 79 | /// 80 | short Person::getAge() const 81 | { 82 | return age; 83 | } 84 | 85 | ``` 86 | 87 | 88 | To use the person class: 89 | 90 | ```cpp 91 | #include 92 | 93 | #include "Person.h" 94 | 95 | int main() 96 | { 97 | Person person1 = Person("Ryan", "Rickgauer", 26); 98 | bool personCanDrive = person1.ableToDrive(); // true 99 | 100 | Person person2 = Person("Tony", "Soprano", 12); 101 | personCanDrive = person2.ableToDrive(); // false 102 | } 103 | ``` 104 | 105 | 106 | ## Inheritance 107 | 108 | 109 | C++ supports inheritance. This example will make a base class called `Vehicle`, and 2 additional classes that inherit from the base class. 110 | 111 | 112 | ```cpp 113 | #include 114 | #include 115 | 116 | // Base vehicle class 117 | class Vehicle 118 | { 119 | 120 | public: 121 | void printMake() { 122 | std::cout << make << std::endl; 123 | } 124 | 125 | protected: 126 | std::string make; 127 | }; 128 | 129 | ``` 130 | 131 | 132 | Now, let's make 2 child classes that inherit from the `Vehicle` class: 133 | 134 | ```cpp 135 | // Inherits from the vehicle class 136 | class Ford : public Vehicle 137 | { 138 | public: 139 | Ford() { 140 | make = "Ford"; 141 | } 142 | }; 143 | 144 | 145 | ``` 146 | 147 | And here is another child class: 148 | 149 | ```cpp 150 | // Inherits from the vehicle class 151 | class Honda : public Vehicle 152 | { 153 | public: 154 | Honda() { 155 | make = "Honda"; 156 | } 157 | }; 158 | 159 | ``` 160 | 161 | Now, let's see how to create these classes: 162 | 163 | ```cpp 164 | // main logic 165 | int main() 166 | { 167 | Ford ford = Ford(); 168 | ford.printMake(); // "Ford" 169 | 170 | Honda honda = Honda(); 171 | honda.printMake(); // "Honda" 172 | } 173 | ``` 174 | -------------------------------------------------------------------------------- /entries/cpp-enums.md: -------------------------------------------------------------------------------- 1 | 2 | This is a quick review on enums in C++. 3 | 4 | To create an enum: 5 | 6 | ```cpp 7 | enum class FoodCategories { 8 | VEGETABLES, 9 | FRUIT, 10 | MEAT, 11 | DAIRY, 12 | }; 13 | ``` 14 | 15 | To use `FoodCategories`: 16 | 17 | ```cpp 18 | std::cout << (short)FoodCategories::VEGETABLES << std::endl; // 0 19 | std::cout << (short)FoodCategories::FRUIT << std::endl; // 1 20 | std::cout << (short)FoodCategories::MEAT << std::endl; // 2 21 | std::cout << (short)FoodCategories::DAIRY << std::endl; // 3 22 | ``` 23 | 24 | You can also use enums as a return type for functions: 25 | 26 | ```cpp 27 | static FoodCategories getVegetables() 28 | { 29 | return FoodCategories::VEGETABLES; 30 | } 31 | 32 | static FoodCategories getFruit() 33 | { 34 | return FoodCategories::FRUIT; 35 | } 36 | 37 | static FoodCategories getMeat() 38 | { 39 | return FoodCategories::MEAT; 40 | } 41 | 42 | static FoodCategories getDairy() 43 | { 44 | return FoodCategories::DAIRY; 45 | } 46 | ``` 47 | 48 | Furthermore, you can use them in switch/if-statements: 49 | 50 | ```cpp 51 | FoodCategories foodCategory = getVegetables(); 52 | 53 | switch (foodCategory) 54 | { 55 | case FoodCategories::VEGETABLES: 56 | std::cout << "Veggies"; 57 | break; 58 | 59 | case FoodCategories::FRUIT: 60 | std::cout << "Fruit"; 61 | break; 62 | 63 | case FoodCategories::MEAT: 64 | std::cout << "Meat"; 65 | break; 66 | 67 | case FoodCategories::DAIRY: 68 | std::cout << "Dairy"; 69 | break; 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /entries/csharp-dialogs.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Selecting a File 4 | 5 | To select a folder, you can utilize the `OpenFileDialog` class. The following example demonstrates how to select a Markdown file: 6 | 7 | ```csharp 8 | public static bool TrySelectingFile(out FileInfo? selectedFile) 9 | { 10 | selectedFile = null; 11 | 12 | OpenFileDialog dialog = new() 13 | { 14 | DefaultExt = ".md", 15 | Filter = "Markdown (.md)|*.md", 16 | Multiselect = false, 17 | }; 18 | 19 | if (!dialog.ShowDialog()) 20 | { 21 | return false; 22 | } 23 | 24 | selectedFile = new(dialog.FileName); 25 | return true; 26 | } 27 | ``` 28 | 29 | [Documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.openfiledialog) 30 | 31 | 32 | 33 | ## Selecting a Folder 34 | 35 | To select a folder, you can utilize the `FolderBrowserDialog` class: 36 | 37 | ```csharp 38 | public static bool TrySelectFolder(out DirectoryInfo selectedFolder) 39 | { 40 | selectedFolder = default; 41 | 42 | // create an instance of a folder browser dialog 43 | using FolderBrowserDialog folderDialog = new(); 44 | 45 | // display the dialog 46 | DialogResult result = folderDialog.ShowDialog(); 47 | 48 | // if user did not hit ok return false 49 | if (result != DialogResult.OK) 50 | { 51 | return false; 52 | } 53 | 54 | // return the selected folder 55 | selectedFolder = new(folderDialog.SelectedPath); 56 | 57 | return true; 58 | } 59 | ``` 60 | 61 | 62 | [Documentation](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.folderbrowserdialog) 63 | 64 | 65 | 66 | ## Opening a Document 67 | 68 | The provided function below opens the specified file using the user's default program. For instance, if the file was a Word Document, it would open the file in Microsoft Word. This also works for URL's. 69 | 70 | ```csharp 71 | public static void ViewDocument(string fileName) 72 | { 73 | ProcessStartInfo startInfo = new(filename) 74 | { 75 | UseShellExecute = true, 76 | }; 77 | 78 | Process.Start(startInfo); 79 | } 80 | ``` 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /entries/css-shadows-snippet.md: -------------------------------------------------------------------------------- 1 | This css tip adds a great looking [box shadow](https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow) to your web components. I always apply this styling to all of my [bootstrap cards](https://getbootstrap.com/docs/4.5/components/card/). 2 | 3 | All you have to do is add this to your css file: 4 | 5 | ```css 6 | .card { 7 | box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.18); 8 | } 9 | ``` 10 | 11 | I got this idea from Steve Schoger's [tweet](https://twitter.com/steveschoger/status/877209916179709955). This tweet is part a [series of tweets](https://twitter.com/i/events/994601867987619840) that offer some pretty amazing design advice. You should check them out. 12 | -------------------------------------------------------------------------------- /entries/daily-omg-facts.md: -------------------------------------------------------------------------------- 1 | 2 | At the start of the new year, I got myself a boxed daily calendar that has "crazy, hard to believe" facts for each entry. So here they are: 3 | 4 | * **1-11-22** — Light doesn't always travel at the speed of light (SOL). SOL is only constant in a vacuum. When it passes through matter it can slow down. Photons pass through water at 3/4ths the regular speed of light. Light was once slowed down to 38mph. 5 | * **1-12-22** — The number of possible ways of playing the first four moves per side in a game of chess is 318,979,564,000. -------------------------------------------------------------------------------- /entries/dotnet-custom-attribute-methods.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | These functions are some that I use for handling custom attributes for properties in my C# classes. 4 | 5 | 6 | ```csharp 7 | /// 8 | /// Get a list of PropertyInfo's of the given source Type that are assigned the specified attribute 9 | /// 10 | /// 11 | /// 12 | /// 13 | public static List GetPropertiesWithAttribute(Type source, Type attribute) 14 | { 15 | List properties = (source).GetProperties().Where(x => x.GetCustomAttributes(attribute, true).Any()).ToList(); 16 | 17 | return properties ?? new(); 18 | } 19 | 20 | /// 21 | /// Get the specified CustomAttribute from the given PropertyInfo 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static T? GetAttributeFromProperty(PropertyInfo prop) 27 | { 28 | var propertyCustomAttributes = prop.GetCustomAttributes(false); 29 | var customAttr = propertyCustomAttributes.Where(x => x.GetType().Name == typeof(T).Name).FirstOrDefault(); 30 | 31 | if (customAttr is null) 32 | { 33 | return default; 34 | } 35 | 36 | return (T)customAttr; 37 | } 38 | ``` 39 | 40 | 41 | ## Example 42 | 43 | 44 | Let's take a look at how to use these methods. 45 | 46 | 47 | 48 | ### Custom Attribute Class 49 | 50 | Here is a custom attribute class that I created: 51 | 52 | 53 | ```csharp 54 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] 55 | public class SqlSelectColumnsAttribute : Attribute, ISqlColumnAttribute 56 | { 57 | public string Name { get; set; } 58 | 59 | public SqlSelectColumnsAttribute(string name) 60 | { 61 | Name = name; 62 | } 63 | } 64 | ``` 65 | 66 | ### Attribute Usage 67 | 68 | 69 | Here is a class with some prperties that use the `SqlSelectColumnsAttribute` attribute: 70 | 71 | ```csharp 72 | public class Topic 73 | { 74 | [SqlSelectColumns("topic_id")] 75 | public uint? Id { get; set; } 76 | 77 | [SqlSelectColumns("topic_name")] 78 | public string? Name { get; set; } 79 | 80 | public DateTime? CreatedOn { get; set; } 81 | } 82 | ``` 83 | 84 | 85 | ### Using the attribute methods 86 | 87 | To use the mapper utility methods: 88 | 89 | ```csharp 90 | // get a list of PropertyInfos that have the SqlSelectColumns attribute 91 | List propertiesWithAttribute = GetPropertiesWithAttribute(typeof(Topic), typeof(SqlSelectColumnsAttribute)); // Id, Name 92 | 93 | // print each property attribute name 94 | foreach (var prop in propertiesWithAttribute) 95 | { 96 | SqlSelectColumnsAttribute? attr = GetAttributeFromProperty(prop); 97 | 98 | var attributeColumnValue = attr.Name; 99 | var propertyName = prop.Name; 100 | 101 | Console.WriteLine($"The attribute value for {propertyName} is: {attributeColumnValue}"); 102 | } 103 | ``` -------------------------------------------------------------------------------- /entries/favorite-blogs.md: -------------------------------------------------------------------------------- 1 | 2 | These are some of my personal favorite blogs about software development and design. 3 | 4 | * https://markdotto.com/ 5 | * https://vanschneider.com/blog 6 | * https://dotdev.co/ 7 | * https://github.com/kilimchoi/engineering-blogs 8 | * https://devblogs.co/ 9 | * https://tom.preston-werner.com/ 10 | * https://gomakethings.com/articles/ 11 | * https://tomanistor.com/#blog -------------------------------------------------------------------------------- /entries/first-committ-at-new-job.md: -------------------------------------------------------------------------------- 1 | I'm not sure if you're aware, but in February of this year, I accepted an offer for a Software Engineer role at [Premier International](https://www.premier-international.com/). They are a small, data-migration company located in the downtown Chicago. Prior to my June 1st start date, I was very anxious about getting started and writing some actual code. 2 | 3 | One of my responsibilities include maintaining and improving their in-house software that the consultants use to migrate their client's data. You can read more about the software [here](https://www.premier-international.com/applaud). The back-end code is written in COBOL and C++, and the front end is written in VB6 and C#. However, one of the features for next update is porting the GUI into all C# code. Very exciting. 4 | 5 |
6 | 7 | 8 | 9 |
10 | 11 | I have been working here for exactly one month, and today I finally made my first code change that got committed into the working source code! It was a COBOL bug fix that took about a week to fix. And much of it I was working with a Senior Engineer. Regardless, once the bug got sorted out and working, I got to add my name and comments to the change log, and push my changes to the code-base! 12 | 13 | It might not seem like much, but to me this is one of the proudest moments of my life. It's starting to feel like all the school and learning is actually coming together, and I am finally writing some professional, enterprise level code. If anything, this might serve as a source of inspiration to someone else who feels lost or unmotivated. I am telling you firsthand that all the late nights coding away are worth it to get to this point. Trust me. -------------------------------------------------------------------------------- /entries/first-contribution.md: -------------------------------------------------------------------------------- 1 | Today, I finally made my first contribution to a repository! The project is called [Public-APIs](https://github.com/n0shake/Public-APIs), and the commit can be viewed [here](https://github.com/n0shake/Public-APIs/commit/fbe0e58e9e4e7f8af03cebf4c27f5a622dfef396). 2 | 3 | Even though the repo is just a giant list and it wasn't any code changes, I am still proud of it. Also, it makes me want to make some actual code commits to some repositories. Sorry for the short post, but that's all I got for this one. Hopefully I will be making more contributions soon. 4 | 5 | ![Fist bump gif](https://media.giphy.com/media/3ov9k9dPpDdoBvZasE/giphy.gif) 6 | 7 | -------------------------------------------------------------------------------- /entries/first-custom-built-pc.md: -------------------------------------------------------------------------------- 1 | For Christmas this year, I decided it was time to build myself a custom PC. I didn't really know what I was doing, but I knew I was going to be writing code on it and occasionally playing [World of Warcraft](https://worldofwarcraft.com/en-us/). 2 | 3 | It took me about 8 hours and countless youtube videos to get it going. However, once I turned it on for the first time and it worked, I was very proud of myself. 4 | 5 | 6 | 7 | ## Part list 8 | 9 | [PCPartPicker Part List](https://pcpartpicker.com/list/tmRjRT) 10 | 11 | Part | Item 12 | :----|:---- 13 | **CPU** | [Intel Core i7-10700K 3.8 GHz 8-Core Processor](https://pcpartpicker.com/product/yhxbt6/intel-core-i7-10700k-38-ghz-8-core-processor-bx8070110700k) 14 | **CPU Cooler** | [Corsair iCUE H100i RGB PRO XT 75 CFM Liquid CPU Cooler](https://pcpartpicker.com/product/B6pmP6/corsair-icue-h100i-rgb-pro-xt-75-cfm-liquid-cpu-cooler-cw-9060043-ww) 15 | **Motherboard** | [MSI Z490-A PRO ATX LGA1200 Motherboard](https://pcpartpicker.com/product/KXpmP6/msi-z490-a-pro-atx-lga1200-motherboard-z490-a-pro) 16 | **Memory** | [G.Skill Ripjaws V Series 32 GB (2 x 16 GB) DDR4-3200 CL16 Memory](https://pcpartpicker.com/product/kXbkcf/gskill-memory-f43200c16d32gvk) 17 | **Storage** | [Samsung 970 Evo 500 GB M.2-2280 NVME Solid State Drive](https://pcpartpicker.com/product/P4ZFf7/samsung-970-evo-500gb-m2-2280-solid-state-drive-mz-v7e500bw) 18 | **Video Card** | [MSI Radeon RX 570 8 GB ARMOR OC Video Card](https://pcpartpicker.com/product/3JdFf7/msi-radeon-rx-570-8gb-armor-oc-video-card-rx-570-armor-8g-oc) 19 | **Case** | [Fractal Design Meshify C ATX Mid Tower Case](https://pcpartpicker.com/product/Y6Crxr/fractal-design-meshify-c-atx-mid-tower-case-fd-ca-mesh-c-bko-tg) 20 | **Power Supply** | [Corsair RM (2019) 750 W 80+ Gold Certified Fully Modular ATX Power Supply](https://pcpartpicker.com/product/6Y66Mp/corsair-rm-2019-750-w-80-gold-certified-fully-modular-atx-power-supply-cp-9020195-na) 21 | -------------------------------------------------------------------------------- /entries/flask-jinja-macros.md: -------------------------------------------------------------------------------- 1 | 2 | This is an example of calling a [Jinja macro](https://jinja.palletsprojects.com/en/3.1.x/templates/#macros) in python to get its output. 3 | 4 | ## Jinja Macro File 5 | 6 | Here is a simple macro that takes in a collection of user objects and makes a list of HTML items. The file is named `users-macro.html`. 7 | 8 | ```html 9 | {% macro create_users_list(users) -%} 10 | 11 |
    12 | {% for user in happenings %} 13 |
  • {{ user.name_first }} {{ user.name_last }}
  • 14 | {% endfor %} 15 |
16 | 17 | {%- endmacro %} 18 | ``` 19 | 20 | 21 | ## Calling the macro 22 | 23 | Let's create a function that calls a jinja macro: 24 | 25 | ```py 26 | import flask 27 | 28 | def run_jinja_macro(file_name: str, macro: str, *args): 29 | jinja_macro = flask.get_template_attribute(file_name, macro) 30 | 31 | result = jinja_macro(*args) 32 | 33 | return result 34 | ``` 35 | 36 | 37 | To call this function: 38 | 39 | ```py 40 | 41 | # data for the macro 42 | users = [ 43 | dict(id=1, name_first='Ryan', name_last='Rickgauer'), 44 | dict(id=2, name_first='George', name_last='Washington'), 45 | dict(id=3, name_first='John', name_last='Smith'), 46 | ] 47 | 48 | html = run_jinja_macro('users-macro.html', 'create_users_list', users) 49 | 50 | print(html) 51 | ``` 52 | -------------------------------------------------------------------------------- /entries/flask-request-url-properties.md: -------------------------------------------------------------------------------- 1 | This post is a table containing some useful [Flask Request](https://flask.palletsprojects.com/en/2.0.x/api/#flask.Request) properties that contain information about the URL. 2 | 3 | ## Properties 4 | 5 | The table below will use the following url as the example: 6 | 7 | ```http 8 | https://example.com/search/products?name=Ryan&age=25 9 | ``` 10 | 11 | --- 12 | 13 | Property | Value 14 | --- | --- 15 | `base_url` | https://example.com/search/products 16 | `full_path` | /search/products?name=Ryan&age=25 17 | `host_url` | https://example.com/ 18 | `host` | example.com 19 | `path` | /search/products 20 | `query_string` | b'name=Ryan&age=25' 21 | `remote_addr` | 11.22.33.66 (the client's IP address) 22 | `url` | https://example.com/search/products?name=Ryan&age=25 23 | `url_root` | https://example.com/ 24 | `url_rule` | /search/products -------------------------------------------------------------------------------- /entries/git-commands.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Branches 4 | 5 | ### Creating a new branch 6 | 7 | To create a new branch and then switch to it: 8 | 9 | ```shell 10 | git checkout -b newBranchName 11 | ``` 12 | 13 | From here, you can do all the changes you want in the branch including doing commits. They will be added to the branch. 14 | 15 | 16 | ### Work on an existing branch 17 | 18 | If you already have a branch and you want to work on it, you can switch to it by using: 19 | 20 | ```shell 21 | git checkout branchName 22 | ``` 23 | 24 | ### Pushing changes to the new branch 25 | 26 | When you are ready to push your *first* commits to the new branch, do the following: 27 | 28 | ```shell 29 | git push --set-upstream origin newBranchName 30 | ``` 31 | 32 | This will push your commits to your new branch and set the upstream to your new branch. After this first push, any subsequent commit pushes you want to make to your new branch you can just do the normal push: 33 | 34 | ```shell 35 | git push 36 | ``` 37 | 38 | ### Merging a branch 39 | 40 | When you are ready to merge a branch into another, go to your repository on GitHub and it will walk you through the steps. 41 | 42 | After that, you need to reconcile your changes onto your local repo. To do this, *switch to the master branch* and use these commands: 43 | 44 | ```shell 45 | git add * 46 | git stash 47 | git pull 48 | ``` 49 | 50 | ### Additional commands 51 | 52 | To list the local branches: 53 | 54 | ```shell 55 | git branch 56 | ``` 57 | 58 | To delete a local branch: 59 | 60 | ```shell 61 | git branch -d 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /entries/hashing-passwords-in-php.md: -------------------------------------------------------------------------------- 1 | Hashing a password in php is pretty straightforward. The a built in functions ```password_hash()``` and ```password_verify()``` do most of the work for you. For a more in depth tutorial, check out the official docs for [password hash](https://www.php.net/manual/en/function.password-hash.php) and [password verify](https://www.php.net/manual/en/function.password-verify.php). 2 | 3 | ## Password Hash 4 | 5 | ```password_hash()``` returns a new password hash using a strong one-way hashing algorithm. To call ```password_hash()```, use it like this: 6 | 7 | ```php 8 | $hashedPassword = password_hash($password, PASSWORD_DEFAULT); 9 | ``` 10 | 11 | *Note:* the hashed password can be up to 255 characters long, so you need to be sure to make the length of the password field in your database at least 255 characters long. 12 | 13 | ## Password Verify 14 | 15 | ```password_verify()``` verifies that the given hash matches the given password. To call ```password_verify()```, use it like this: 16 | 17 | ```php 18 | $result = password_verify($password, $hashedPassword); 19 | ``` 20 | 21 | The result is a bool (true or false) that tells if the first argument matches the second argument (the hashed password). 22 | 23 | ## Example 24 | 25 | Say we have a web application that stores usernames and passwords. 26 | 27 | To insert a new username and password combination using pdo, we could do something like this: 28 | 29 | ```php 30 | // insert a new username record into the database 31 | function insertNewUsername($username, $password) { 32 | $pdo = dbConnect(); 33 | $sql = $pdo->prepare('INSERT INTO Users (username, password) VALUES (:username, :password)'); 34 | 35 | // sanitize and bind username 36 | $username = filter_var($username, FILTER_SANITIZE_STRING); 37 | $sql->bindParam(':username', $username, PDO::PARAM_STR); 38 | 39 | // sanitize, hash, and bind password 40 | $password = filter_var($password, FILTER_SANITIZE_STRING); 41 | $hashedPassword = password_hash($password, PASSWORD_DEFAULT); 42 | $sql->bindParam(':password', $hashedPassword, PDO::PARAM_STR); 43 | 44 | $sql->execute(); 45 | 46 | return $sql; 47 | } 48 | ``` 49 | 50 | Now, let's say a user wanted to login to their account by entering in their username and password. We could validate the login attempt like this: 51 | 52 | ```php 53 | function isValidUsernameAndPassword($username, $password) { 54 | $pdo = dbConnect(); 55 | $sql = $pdo->prepare('SELECT password FROM Users WHERE username=:username LIMIT 1'); 56 | 57 | // sanitize and bind username 58 | $username = filter_var($username, FILTER_SANITIZE_STRING); 59 | $sql->bindParam(':username', $username, PDO::PARAM_STR); 60 | 61 | $sql->execute(); 62 | 63 | // username does not exist 64 | if ($sql->rowCount() != 1) { 65 | return false; 66 | } 67 | 68 | // username exists, now check if the passwords match 69 | else { 70 | $hash = $sql->fetch(PDO::FETCH_ASSOC); 71 | $hash = $hash['password']; 72 | 73 | return password_verify($password, $hash); 74 | } 75 | } 76 | ``` 77 | 78 | If the function returns true, then the passwords match and the login attempt is valid. -------------------------------------------------------------------------------- /entries/icu-guide.md: -------------------------------------------------------------------------------- 1 | ## Docs 2 | 3 | * [ICU User Guide](https://unicode-org.github.io/icu/userguide/) 4 | * [List of available character sets (codepages)](https://www.iana.org/assignments/character-sets/character-sets.xhtml) 5 | * [Faqs](https://unicode-org.github.io/icu/userguide/icufaq/#using-icu) 6 | 7 | ## Converter Life Cycle 8 | 9 | https://unicode-org.github.io/icu/userguide/conversion/converters.html#converter-life-cycle 10 | 11 | 1. First, open up the converter with a specified name (or alias name). 12 | 2. Convert the data 13 | 3. Clean up (close) the converter. 14 | 15 | 16 | 17 | ## Creating a converter 18 | 19 | https://unicode-org.github.io/icu/userguide/conversion/converters.html#creating-a-converter 20 | 21 | You can create a converter 4 different ways, but the first two seem to be the best option for us: 22 | 23 | 1. By name 24 | 2. By codepage number 25 | 26 | #### By name 27 | 28 | ```cpp 29 | UConverter *conv = ucnv_open("shift_jis", &myError); 30 | ``` 31 | 32 | The list of available names can be found [here](https://www.iana.org/assignments/character-sets/character-sets.xhtml). 33 | 34 | 35 | #### By codepage number 36 | 37 | 38 | ```cpp 39 | ucnv_openCCSID(37, UCNV_IBM, &myErr); 40 | ``` 41 | 42 | 43 | ## Conversion Methods 44 | 45 | https://unicode-org.github.io/icu/userguide/conversion/converters.html#modes-of-conversion 46 | 47 | The appropriate conversion methods are `ucnv_fromUChars` and `ucnv_toUChars`. 48 | 49 | 50 | **Converting From UTF** 51 | 52 | ```cpp 53 | len = ucnv_fromUChars(conv, target, targetLen, source, sourceLen, &status); 54 | ``` 55 | 56 | 57 | **Converting to UTF** 58 | 59 | ```cpp 60 | len = ucnv_toUChars(conv, target, targetSize, source, sourceLen, &status); 61 | ``` 62 | 63 | 64 | ## Closing the converter 65 | 66 | To close the converter, you can do: 67 | 68 | ```cpp 69 | ucnv_close(conv); 70 | ``` 71 | 72 | ## Notes 73 | 74 | After reading through the docs, I believe we must first convert the Ascii data to UTF-16 first, then convert it to UTF-8. 75 | 76 | > Since ICU uses Unicode (UTF-16) internally, all converters convert between UTF-16 (with the endianness according to the current platform) and another encoding. 77 | 78 | Also, 79 | 80 | > In order to use the collation, text boundary analysis, formatting or other ICU APIs, you must use Unicode strings. In order to get Unicode strings from your native > codepage, you can use the conversion API. 81 | 82 | This might be the solution: 83 | 84 | ```cpp 85 | 86 | UConverter *conv; // icu converter 87 | const char *source; // ascii data array 88 | int32_t sourceSize = sizeof(source); // size of ascii array data 89 | UErrorCode status = U_ZERO_ERROR; // error flag 90 | UChar *targetUnicode; // icu utf-16 array 91 | char *targetUtf8; // array holding the utf-8 data 92 | int32_t targetSize = sizeof(targetUnicode); // size of the utf-16 array 93 | 94 | // Create a converter to handle ascii to unicode 95 | conv = ucnv_open("US-ASCII", &status); 96 | 97 | // Convert the ascii to utf-16 98 | ucnv_toUChars(conv, targetUnicode, targetSize, source, sourceSize, &status); 99 | 100 | // Set the converter to convert to utf-8 data 101 | conv = ucnv_open("utf-8", &status); 102 | 103 | // Convert the utf-16 to utf-8 104 | ucnv_fromUChars(conv, targetUtf8, sizeof(targetUtf8), targetUnicode, targetSize, &status); 105 | 106 | // Close the converter 107 | ucnv_close(conv); 108 | ``` 109 | 110 | 111 | 112 | 113 | ## Examples 114 | 115 | https://github.com/unicode-org/icu/blob/master/icu4c/source/samples/ucnv/convsamp.cpp 116 | 117 | 118 | -------------------------------------------------------------------------------- /entries/installing-babel.md: -------------------------------------------------------------------------------- 1 | 2 | [Babel](https://babeljs.io/) is a JavaScript compiler. This is how to set it up in a project. 3 | 4 | ## Installation 5 | 6 | In the terminal, cd into the project directory. Then, you need to create a `package.json` via: 7 | 8 | ```shell 9 | npm init 10 | ``` 11 | 12 | Now it's time to install the Babel.js [core](https://babeljs.io/docs/en/usage#core-library) and [CLI](https://babeljs.io/docs/en/babel-cli) libaries: 13 | 14 | ```shell 15 | npm install --save-dev @babel/core @babel/cli 16 | ``` 17 | 18 | 19 | *Optional*. Install the [plugin-proposal-class-properties plugin](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties): 20 | 21 | ```shell 22 | npm install --save-dev @babel/plugin-proposal-class-properties 23 | ``` 24 | 25 | 26 | ## Usage 27 | 28 | Compile the entire src directory and output it to the lib directory by using either `--out-dir` or `-d`. This doesn't overwrite any other files or directories in lib. 29 | 30 | ```shell 31 | npx babel src --out-dir lib 32 | ``` 33 | 34 | Compile the entire src directory and output it as a single concatenated file. 35 | 36 | ```shell 37 | npx babel src --out-file script-compiled.js 38 | ``` 39 | 40 | To compile a file every time that you change it, use the `--watch` or `-w` option: 41 | 42 | ```shell 43 | npx babel script.js --watch --out-file script-compiled.js 44 | ``` 45 | 46 | Use the `--plugins` option to specify plugins to use in compilation: 47 | 48 | ```shell 49 | npx babel script.js --out-file script-compiled.js --plugins=@babel/proposal-class-properties,@babel/transform-modules-amd 50 | ``` 51 | 52 | Use the `--presets` option to specify presets to use in compilation 53 | 54 | ```shell 55 | npx babel script.js --out-file script-compiled.js --presets=@babel/preset-env,@babel/flow 56 | ``` 57 | 58 | 59 | ## Further Reading 60 | 61 | 62 | * https://babeljs.io/docs/en/babel-cli 63 | -------------------------------------------------------------------------------- /entries/javascript-fetch-api.md: -------------------------------------------------------------------------------- 1 | The [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline, such as requests and responses. It also provides a global `fetch()` method that provides an easy, logical way to fetch resources asynchronously across the network. 2 | 3 | ## Implementation 4 | 5 | To use the `fetch` api: 6 | 7 | ```js 8 | async function postData(url = '', data = {}) { 9 | const response = await fetch(url, { 10 | method: 'POST', // *GET, POST, PUT, DELETE, etc. 11 | 12 | headers: { 13 | 'Content-Type': 'application/json' 14 | // 'Content-Type': 'application/x-www-form-urlencoded', 15 | }, 16 | 17 | body: JSON.stringify(data) // body data type must match "Content-Type" header 18 | }); 19 | 20 | return response; 21 | } 22 | ``` 23 | 24 | ## Usage 25 | 26 | The fetch method returns a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object: 27 | 28 | ```js 29 | // response 30 | const apiResponse = await postData({ 31 | name: 'Ryan Rickgauer', 32 | college: 'Northern Illinois University', 33 | }); 34 | ``` 35 | 36 | 37 | To check if the request was successful: 38 | 39 | ```js 40 | if (apiResponse.ok) { 41 | console.log('Successful request'); 42 | } 43 | else { 44 | console.log('Request was not successful!'); 45 | } 46 | ``` 47 | 48 | 49 | If the response returns some json data, you can retrieve it by using the `json()` method: 50 | 51 | ```js 52 | const responseData = await apiResponse.json(); 53 | console.log(responseData); 54 | ``` 55 | -------------------------------------------------------------------------------- /entries/javascript-tablesort-example.md: -------------------------------------------------------------------------------- 1 | # TableSort Example 2 | 3 | ## Docs 4 | 5 | * [GitHub repo](https://github.com/tristen/tablesort) 6 | * [Demo](http://tristen.ca/tablesort/demo/) 7 | * [Complete example gist](https://gist.github.com/rrickgauer/39fed29080d4d36f7ff1b29c4d780eb8) 8 | 9 | --- 10 | 11 | **Include the table sort files in your HTML file:** 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | **Setup your html table:** 23 | 24 | ```html 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
idfirst_namelast_nameemaildateamount
39 | ``` 40 | 41 | **In your personal JavaScript file execute:** 42 | 43 | ```js 44 | new Tablesort(document.getElementById('table')); 45 | ``` 46 | 47 | **Then, you can click on a table column header and it will sort by that.** 48 | 49 | -------------------------------------------------------------------------------- /entries/jquery-contains.md: -------------------------------------------------------------------------------- 1 | 2 | This is a short example on how to use jQuery's `:contains` selector. 3 | 4 | 5 | ## Example 6 | 7 | 8 | Suppose we have a table like this: 9 | 10 | 11 |
12 | Example table 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
First nameLast nameEmailCountryDob
JohnyWalkerjohny.walker@whiskey.comScotland1/1/1920
nikhilVartaknikhil.vartak@hotmail.co.inindia12/5/1986
PeterJamesjames_peter@hotmail.comgermany5/10/1960
nikhilVartaknikhil.vartak@hotmail.comindia11/27/1984
PeterJamesjames_peter@hotmail.comgermany5/10/1960
jackDanielj'daniels@whiskey.comUSA1/10/1846
nikhilVartaknikhil.vartak@hotmail.co.inindia12/5/1986
PeterJamesjames_peter@hotmail.comgermany5/10/1960
jackDanielj'daniels@whiskey.comUSA1/10/1846
jackDanielj'daniels@whiskey.comUSA1/10/1846
MarkAndersonanderson_m@gmail.comcanada2/29/1980
jackDanielj'daniels@whiskey.comUSA1/10/1846
nikhilVartaknikhil.vartak@hotmail.co.inindia12/5/1986
jackDanielj'daniels@whiskey.comUSA1/10/1846
PeterJamesjames_peter@hotmail.comgermany5/10/1960
34 | 35 |
36 | 37 | 38 | If we have an input, we could use this code to filter the results: 39 | 40 | ```js 41 | 42 | function searchTable() { 43 | var input = $('#search-input').val(); 44 | $('#data-table').find('tr:not(:contains(' + input + '))').addClass('d-none'); // hide non matches 45 | $('#data-table').find('tr:contains(' + input + ')').removeClass('d-none'); // show mathing tables 46 | } 47 | 48 | ``` 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /entries/markdown-cheatsheet.md: -------------------------------------------------------------------------------- 1 | This is a cheatsheet I made that covers some basic markdown syntax. I got most of the content from the [Markdown cheatsheet](https://devhints.io/markdown) by [devhints.io](https://devhints.io/). 2 | 3 | 4 | 5 | ### Headers 6 | 7 | ```md 8 | # h1 9 | ## h2 10 | ### h3 11 | #### h4 12 | ##### h5 13 | ###### h6 14 | 15 | Header 1 16 | ======== 17 | 18 | 19 | Header 2 20 | -------- 21 | ``` 22 | 23 | ### Code 24 | 25 | `Inline code` 26 | 27 | ``2` inline code`` 28 | 29 | ```3` inline code``` 30 | 31 | Unfenced code block 32 | 33 | ``` 34 | Fenced code block 35 | ``` 36 | 37 | ~~~ 38 | Fenced code block 39 | ~~~ 40 | 41 | ```js 42 | // Fenced JS code block 43 | ``` 44 | 45 | 46 | ### Tables 47 | 48 | 49 | ```md 50 | | Column 1 Heading | Column 2 Heading | 51 | | ---------------- | ---------------- | 52 | | Some content | Other content | 53 | 54 | 55 | Column 1 Heading | Column 2 Heading 56 | --- | --- 57 | Some content | Other content 58 | ``` 59 | 60 | 61 | 62 | ### Emphasis 63 | 64 | 65 | ```md 66 | *italic* 67 | _italic_ 68 | 69 | **bold** 70 | __bold__ 71 | 72 | `code` 73 | 74 | ~~Strikethrough~~ 75 | 76 | Subscript~example~ 77 | Superscript^example^ 78 | ``` 79 | 80 | 81 | ### Links 82 | 83 | ```md 84 | [link](http://google.com) 85 | 86 | [link][google] 87 | [google]: http://google.com 88 | 89 | 90 | ``` 91 | 92 | 93 | ### Blockquotes 94 | 95 | 96 | ```md 97 | > This is 98 | > a blockquote 99 | > 100 | > > Nested 101 | > > Blockquote 102 | ``` 103 | 104 | ### Lists 105 | 106 | ```md 107 | * Item 1 108 | * Item 2 109 | * sub item 1 110 | * sub item 2 111 | 112 | - Item 1 113 | - Item 2 114 | - sub item 1 115 | - sub item 2 116 | 117 | - [ ] Checkbox off 118 | - [x] Checkbox on 119 | 120 | 1. Item 1 121 | 2. Item 2 122 | ``` 123 | 124 | 125 | 126 | ### Images 127 | 128 | ```md 129 | ![Image alt text](/path/to/img.jpg) 130 | ![Image alt text](/path/to/img.jpg "title") 131 | ![Image alt text][img] 132 | 133 | [img]: http://foo.com/img.jpg 134 | ``` 135 | 136 | ### Horizontal line 137 | 138 | 139 | ```md 140 | ---- 141 | 142 | **** 143 | ``` 144 | 145 | 146 | -------------------------------------------------------------------------------- /entries/mysql-create-stored-procedure.md: -------------------------------------------------------------------------------- 1 | A stored procedure is a segment of declarative SQL statements stored inside the MySQL Server. 2 | 3 | To create a new stored procedure: 4 | 5 | ```sql 6 | DELIMITER $$ 7 | 8 | CREATE PROCEDURE Get_Order_Status ( 9 | IN orderStatus VARCHAR(25) 10 | ) 11 | BEGIN 12 | SELECT o.orderNumber 13 | FROM orders o 14 | WHERE status = orderStatus; 15 | END$$ 16 | 17 | DELIMITER ; 18 | ``` 19 | 20 | 21 | To use this stored procedure: 22 | 23 | ```sql 24 | CALL Get_Order_Status('complete'); 25 | ``` -------------------------------------------------------------------------------- /entries/mysql-stored-functions.md: -------------------------------------------------------------------------------- 1 | A stored function is a special kind stored program that returns a single value. Typically, you use stored functions to encapsulate common formulas or business rules that are reusable among SQL statements or stored programs. 2 | 3 | To create a new stored function: 4 | 5 | ```sql 6 | DELIMITER $$ 7 | 8 | CREATE FUNCTION Customer_Level( 9 | credit DECIMAL(10,2) 10 | ) 11 | RETURNS VARCHAR(20) 12 | DETERMINISTIC 13 | BEGIN 14 | DECLARE customerLevel VARCHAR(20); 15 | 16 | IF credit > 50000 THEN 17 | SET customerLevel = 'PLATINUM'; 18 | ELSEIF (credit >= 50000 AND credit <= 10000) THEN 19 | SET customerLevel = 'GOLD'; 20 | ELSEIF credit < 10000 THEN 21 | SET customerLevel = 'SILVER'; 22 | END IF; 23 | 24 | -- return the customer level 25 | RETURN (customerLevel); 26 | END$$ 27 | DELIMITER ; 28 | ``` 29 | 30 | 31 | To use this function: 32 | 33 | ```sql 34 | SELECT c.id, Customer_Level(c.credit) FROM Customers c GROUP BY c.id; 35 | ``` 36 | -------------------------------------------------------------------------------- /entries/mysqldump-notes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Table Schemas 4 | 5 | This command dumps the database tables, views, procedures, functions, events, and triggers into a single file: 6 | 7 | ```sh 8 | mysqldump -u {user_name} -h {ip_address} -p ^ 9 | --databases {DATABASE_NAME} ^ 10 | --no-create-db ^ 11 | --routines ^ 12 | --events ^ 13 | --triggers ^ 14 | --skip-quote-names ^ 15 | --no-data ^ 16 | --result-file "{output_file}" 17 | ``` 18 | 19 | 20 | ## Reference Tables 21 | 22 | Most of my databases have reference tables, or predefined constants, that are used as foreign keys for other tables. 23 | 24 | For example, if I was making an application to store game data for each of the major sports leagues in the US, I would probably have a table in there called `Leagues` that has 4 rows representing the 4 major leagues: NFL, NBA, MLB, NHL. I usually end up mapping these tables to `Enums` in my source code. 25 | 26 | ```sh 27 | mysqldump -u {user_name} -h {ip_address} -p ^ 28 | --no-create-db ^ 29 | --no-create-info ^ 30 | --replace ^ 31 | --order-by-primary ^ 32 | --skip-quote-names ^ 33 | --result-file "{output_file}" ^ 34 | {Database_Name} {Table_Name} {Table_Name} {Table_Name} 35 | ``` 36 | 37 | 38 | ## Batch File 39 | 40 | This is the standard format for a batch file that dumps the table schemas and reference tables into a single file: 41 | 42 | ```sh 43 | mysqldump -u {user_name} -h {ip_address} -p ^ 44 | --databases Turma_Dev ^ 45 | --column-statistics=FALSE ^ 46 | --no-create-db ^ 47 | --routines ^ 48 | --events ^ 49 | --triggers ^ 50 | --skip-quote-names ^ 51 | --no-data ^ 52 | --result-file "C:\xampp\htdocs\files\turma\sql\ddl\.schemas.sql" 53 | 54 | 55 | mysqldump -u {user_name} -h {ip_address} -p ^ 56 | --column-statistics=FALSE ^ 57 | --no-create-db ^ 58 | --no-create-info ^ 59 | --replace ^ 60 | --order-by-primary ^ 61 | --skip-quote-names ^ 62 | --result-file "C:\xampp\htdocs\files\turma\sql\ddl\.data.sql" ^ 63 | Turma_Dev Error_Message_Groups Error_Messages Server_Invitation_Status 64 | 65 | @echo off 66 | cd "C:\xampp\htdocs\files\turma\sql\ddl" 67 | 68 | type ".schemas.sql" ".data.sql" > "dump.sql" 69 | 70 | del ".schemas.sql" /Q 71 | del ".data.sql" /Q 72 | 73 | pause 74 | ``` 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /entries/number-conversions.md: -------------------------------------------------------------------------------- 1 |

Content

2 |
    3 |
  1. Background
  2. 4 |
  3. Function Table
  4. 5 |
  5. For Loops
  6. 6 |
7 | 8 |

Background

9 |

This is going to be an ongoing project where that reviews some functions/code snippets and the different syntax between common programming languages.

10 | 11 | 12 |

Function Table

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Functions:PHPJavaScriptPython
String lengthstrlen($str)str.lengthlen(str)
Array sizecount($array)array.lengthlen(array)
Creating a functionfunction print()function print()def function:
47 | 48 |

For Loops

49 |

PHP

50 |
for ($x = 0; $x <= 100; $x+=10) {
51 |     echo "The number is: $x";
52 | }
53 | 54 |

JavaScript

55 |
for (i = 0; i < len; i++) {
56 |   text += cars[i];
57 | }
58 | 59 |

Python

60 |
Looping through a list
61 |
fruits = ["apple", "banana", "cherry"]
62 | for x in fruits:
63 |   print(x)
64 | 65 |
Looping through string index
66 |
for x in "banana":
67 |   print(x)
68 | 69 |
Looping through a range
70 |
for x in range(6):
71 |   print(x)
72 | 73 | -------------------------------------------------------------------------------- /entries/personal-python-functions.md: -------------------------------------------------------------------------------- 1 | # Personal Python Library 2 | 3 | This is my personal python utilities library that I made so that I can quickly access it from anywhere if needed. This is a work in progress so I will be updating as the time comes. The repo can be found [here](https://github.com/rrickgauer/python-utilities). 4 | 5 | ## Functions 6 | ### Space 7 | This function basically just prints out a specified number of new lines. 8 | 9 | ```python 10 | def space(numSpaces = 1): 11 | for x in range(numSpaces): 12 | print('') 13 | ``` 14 | 15 | ### Beautiful Table 16 | 17 | [Beautiful Table](https://github.com/pri22296/beautifultable) is a module I just discovered the other day. It has come in quite handy, and I highly recommend to anyone that needs an easy way to print out tables in the terminal. 18 | 19 | #### Installation 20 | 21 | ```shell 22 | pip install beautifultable 23 | ``` 24 | 25 | #### Usage 26 | ```python 27 | from beautifultable import BeautifulTable 28 | 29 | def getTable(data, columns=[]): 30 | table = BeautifulTable(max_width=1000) 31 | table.set_style(BeautifulTable.STYLE_COMPACT) 32 | 33 | if len(columns) > 0: 34 | table.column_headers=columns 35 | 36 | for row in data: 37 | table.append_row(row) 38 | 39 | table.column_alignments = BeautifulTable.ALIGN_LEFT 40 | return table 41 | ``` 42 | -------------------------------------------------------------------------------- /entries/php-bootstrap-alert-function.md: -------------------------------------------------------------------------------- 1 | Many times, I have had to create a [bootstrap alert](https://getbootstrap.com/docs/4.5/components/alerts/) in my web applications. So many in fact, I started writing a php function that can create it for me. Here is my snippet. 2 | 3 | This is the function you can use throughout your code: 4 | 5 | ```php 6 | // returns a bootstrap alert 7 | function getAlert($message, $alertType = 'success') { 8 | return " 9 |
10 | $message 11 | 14 |
"; 15 | } 16 | ``` 17 | 18 | Then, whenever you need an alert, you can use it by doing this: 19 | 20 | ```php 21 | echo getAlert('Your message goes here', 'warning'); 22 | ``` 23 | 24 | ## Parameters 25 | 26 | The ```$alertType``` defaults to *success*, but bootstrap offers [several different options](https://getbootstrap.com/docs/4.5/components/alerts/#examples) for the type of alert you have. 27 | 28 | 29 | ![Image](https://www.jquery-az.com/wp-content/uploads/2018/01/20-1-Bootstrap-4-alerts.png) 30 | 31 | ## Further Reading 32 | 33 | My [Bootstrap Notes post](https://www.ryanrickgauer.com/blog/entries.php?entryID=11) contains several of my favorite bootstrap code snippets. Check it out! -------------------------------------------------------------------------------- /entries/pin-batch-files-to-taskbar.md: -------------------------------------------------------------------------------- 1 | My computer at work is a Windows. Lately I have been having to use batch files for moving documents around. I wanted a way to pin a batch file to my taskbar, but Windows doesn't have a way to do this. After some research, the answered offered [here](https://superuser.com/questions/100249/how-to-pin-either-a-shortcut-or-a-batch-file-to-the-new-windows-7-8-and-10-task/193255#193255) is one that works for me. 2 | 3 | 1. Create a shortcut to your batch file. 4 | 1. Get into shortcut property and change target to something like: `cmd.exe /C "path-to-your-batch"` 5 | 1. Simply drag your new shortcut to the taskbar. It should now be pinnable. -------------------------------------------------------------------------------- /entries/postgres-notes.md: -------------------------------------------------------------------------------- 1 | 2 | ## Functions 3 | 4 | Create a fuction that returns a text: 5 | 6 | ```sql 7 | CREATE OR REPLACE FUNCTION build_username(first_name TEXT) 8 | RETURNS TEXT 9 | LANGUAGE 'plpgsql' 10 | AS $BODY$ 11 | DECLARE prefix TEXT; 12 | BEGIN 13 | prefix := 'user_' || first_name; 14 | return prefix; 15 | END; 16 | $BODY$; 17 | ``` 18 | 19 | 20 | Create a function that returns table: 21 | 22 | ```sql 23 | CREATE OR REPLACE FUNCTION get_users(age INT) 24 | RETURNS SETOF users 25 | LANGUAGE 'plpgsql' 26 | AS $BODY$ 27 | BEGIN 28 | RETURN query 29 | SELECT u.* 30 | FROM users u 31 | where u.user_age = age; 32 | END; 33 | $BODY$; 34 | ``` 35 | 36 | 37 | ## Procedures 38 | 39 | ```sql 40 | CREATE OR REPLACE PROCEDURE normalize_checklist_item_positions( 41 | IN in_checklist_id INT 42 | ) 43 | LANGUAGE 'plpgsql' 44 | AS $BODY$ 45 | DECLARE public_id CHAR(24); 46 | BEGIN 47 | public_id := get_checklist_public_id(in_checklist_id); 48 | CALL normalize_checklist_item_positions(public_id); 49 | END 50 | $BODY$; 51 | ``` 52 | 53 | 54 | ## Domains 55 | 56 | To create a `username` domain that must be greater than 20 characters: 57 | 58 | ```sql 59 | CREATE DOMAIN username 60 | CHECK (LENGTH(VALUE) > 20); 61 | ``` 62 | 63 | To create a domain with a regex constraint: 64 | 65 | ```sql 66 | CREATE DOMAIN user_nanoid AS TEXT 67 | CHECK (VALUE ~ '^clc_.{20}$'); 68 | ``` 69 | 70 | 71 | 72 | ## Identity Columns 73 | 74 | ```sql 75 | CREATE TABLE table_name ( 76 | id INTEGER NOT NULL UNIQUE GENERATED {ALWAYS | BY DEFAULT} AS IDENTITY PRIMARY KEY, 77 | column_name data_type, 78 | -- additional columns... 79 | ); 80 | ``` 81 | 82 | 83 | ## Further Reading 84 | 85 | * https://wiki.postgresql.org/wiki/Don't_Do_This -------------------------------------------------------------------------------- /entries/prism-examples.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```sh 4 | maybe yes 5 | idk 6 | ``` -------------------------------------------------------------------------------- /entries/python-bs4.md: -------------------------------------------------------------------------------- 1 | [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) is a Python library for pulling data out of HTML and XML files. It's one of the most popular python modules and is pretty easy to use. 2 | 3 | ## Table of Contents 4 | 5 | 6 |
7 | Click me to open 8 | 9 | 10 | 1. [Installation](#installation) 11 | 1. [Usage](#usage) 12 | 1. [Searching for elements](#searching-for-elements) 13 | 14 | 15 |
16 | 17 | ## Installation 18 | 19 | To install Beautiful Soup 4 (BS4), you can just use [pip](https://github.com/pypa/pip): 20 | 21 | ```sh 22 | pip install beautifulsoup4 23 | ``` 24 | 25 | In this post, I am going to use the [requests](https://github.com/psf/requests) python library: 26 | 27 | ```sh 28 | pip install requests 29 | ``` 30 | 31 | Before we get started, be sure to include the modules in your python script: 32 | 33 | ```py 34 | import requests 35 | from bs4 import BeautifulSoup 36 | ``` 37 | 38 | ## Usage 39 | 40 | The first step is to retrieve the html from the website. 41 | 42 | ```py 43 | response = requests.get('http://example.com/') 44 | soup = BeautifulSoup(response.text, 'html.parser') 45 | ``` 46 | 47 | `soup` contains the BS4 object that you can parse. 48 | 49 | There are a few different parsers: html.parse, [lxml](https://lxml.de/), [lxml-xml](https://lxml.de/), and [html5lib](https://github.com/html5lib/html5lib-python). Each has its own [advantages and disadvantages](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser). 50 | 51 | 52 | ### Searching for elements 53 | 54 | To find an element with a specific id: 55 | 56 | ```py 57 | element = soup.find(id='elementTagID') 58 | ``` 59 | 60 | One common task is extracting all the URLs found within a page’s `` tags: 61 | 62 | ```py 63 | for link in soup.find_all('a'): 64 | print(link.get('href')) 65 | 66 | # http://example.com/elsie 67 | # http://example.com/lacie 68 | # http://example.com/tillie 69 | ``` 70 | 71 | To get the link text from the page you can do 72 | 73 | ```py 74 | for link in soup.find_all('a'): 75 | print(link.get_text()) 76 | 77 | # Click here 78 | # another link text 79 | # IDK what else to put 80 | ``` 81 | 82 | To get all link elements with the CSS class `sister`: 83 | 84 | ```python 85 | soup.find_all("a", class_="sister"): 86 | ``` -------------------------------------------------------------------------------- /entries/python-dataclass-serializer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a simple wrapper/method for mapping dictionaries to data classes in python. 4 | 5 | ## Mapper Module 6 | 7 | First, we need to install the [dacite](https://github.com/konradhalas/dacite) libray: 8 | 9 | ```sh 10 | pip install dacite 11 | ``` 12 | 13 | Now, create a new mapper module to handle the dictionary mapping: 14 | 15 | ```py 16 | from __future__ import annotations 17 | import datetime 18 | import uuid 19 | from dacite.core import from_dict 20 | import dacite 21 | from typing import TypeVar, Type, Dict, List 22 | import enum 23 | 24 | T = TypeVar("T") 25 | 26 | def _try_map_date_str(day_str: str): 27 | """Try mapping a date string value""" 28 | 29 | try: 30 | return datetime.date.fromisoformat(day_str) 31 | except: 32 | return datetime.datetime.fromisoformat(day_str).date() 33 | 34 | MAPPER_CONFIG = dacite.Config( 35 | cast = [ 36 | uuid.UUID, 37 | enum.Enum 38 | ], 39 | 40 | type_hooks = { 41 | datetime.datetime: datetime.datetime.fromisoformat, 42 | datetime.date: _try_map_date_str, 43 | datetime.time: datetime.time.fromisoformat, 44 | }, 45 | ) 46 | 47 | 48 | 49 | def map_dicts(data: List[Dict], class_type: Type[T]) -> List[T]: 50 | """Map the dictionaries to the specified type.""" 51 | return [map_dict(d, class_type) for d in data] 52 | 53 | 54 | def map_dict(data: Dict, class_type: Type[T]) -> T: 55 | """Map the given dictionary to the specified type""" 56 | return from_dict(class_type, data, MAPPER_CONFIG) 57 | 58 | 59 | class IMappable: 60 | """Have a domain dataclass inherit this class to use ClassName.from_dict(data).""" 61 | 62 | @classmethod 63 | def from_dicts(cls, data: List[Dict]): 64 | return map_dicts(data, cls) 65 | 66 | @classmethod 67 | def from_dict(cls, data: Dict): 68 | return map_dict(data, cls) 69 | ``` 70 | 71 | ## Usage 72 | 73 | Let's start out with a basic User data class: 74 | 75 | ```py 76 | from __future__ import annotations 77 | from dataclasses import dataclass 78 | from typing import Optional as Opt 79 | from datetime import datetime 80 | import IMappable 81 | 82 | 83 | @dataclass 84 | class User(IMappable): 85 | user_id : Opt[int] = None 86 | email : Opt[str] = None 87 | created_on: Opt[datetime] = None 88 | ``` 89 | 90 | To map dictionaries to a dataclass you can do it like so: 91 | 92 | ```py 93 | import User 94 | 95 | user_dict = dict( 96 | user_id = 1, 97 | email = 'testing@example.com', 98 | created_on = None, 99 | ) 100 | 101 | user_data_class = User.from_dict(user_dict) 102 | print(user_data_class) 103 | ``` -------------------------------------------------------------------------------- /entries/python-file-operations.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | 3 |
4 | Click to expand 5 | 6 | 7 | 1. [Opening a file](#opening-a-file) 8 | 1. [Closing a file](#closing-a-file) 9 | 1. [Reading a file](#reading-a-file) 10 | 1. [Writing to a file](#writing-to-a-file) 11 | 12 | 13 |
14 | 15 | ## Opening a file 16 | 17 | [File operations](https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files) usually start with creating a file object. 18 | 19 | ```py 20 | f = open(fileName, mode) 21 | ``` 22 | 23 | * `fileName` is a string such as *names.txt* 24 | * `mode` must be one of the accepted file modes: 25 | 26 | Mode | Description 27 | :--- | :--- 28 | `r` | Opens a file for reading. (default) 29 | `w` | Opens a file for writing. Creates a new file if it does not exist or truncates the file if it exists. 30 | `x` | Opens a file for exclusive creation. If the file already exists, the operation fails. 31 | `a` | Opens a file for appending at the end of the file without truncating it. Creates a new file if it does not exist. 32 | `t` | Opens in text mode. (default) 33 | `b` | Opens in binary mode. 34 | `+` | Opens a file for updating (reading and writing) 35 | 36 | ### Examples 37 | 38 | ```py 39 | f = open("test.txt") # equivalent to 'r' or 'rt' 40 | f = open("test.txt",'w') # write in text mode 41 | ``` 42 | 43 | ## Closing a file 44 | 45 | When you are done using the file, you should close it via: 46 | 47 | ```py 48 | f.close() 49 | ``` 50 | 51 | You can also use python's `with` function so you don't need to close the file: 52 | 53 | ```py 54 | with open('test.txt', 'w') as f: 55 | # file operations 56 | ``` 57 | 58 | ## Reading a file 59 | 60 | To read the contents of a file line by line by using a *for loop*: 61 | 62 | ```py 63 | with open('test.txt', 'w') as f: 64 | for line in f: 65 | print(line) 66 | ``` 67 | 68 | Or you can use the `readLine()` file method: 69 | 70 | ```py 71 | print(f.readline()) # "This is my first file\n" 72 | print(f.readline()) # "This file\n" 73 | print(f.readline()) # "contains three lines\n" 74 | ``` 75 | 76 | ## Writing to a file 77 | 78 | To write to a file, use the `write()` method. 79 | 80 | ```py 81 | f.write("this is line one \n") 82 | f.write("this is line two \n") 83 | f.write("this is line three \n") 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /entries/python-json-operations.md: -------------------------------------------------------------------------------- 1 | This post will demonstrate how to work with json data in python. The full python documentation can be found [here](https://docs.python.org/3/library/json.html). 2 | 3 | ## Basic Operations 4 | 5 | 6 | ### Reading JSON Data 7 | 8 | To retrieve data written to a json file: 9 | 10 | ```py 11 | def getJsonData(fileName: str): 12 | inputFile = open(fileName, 'r') 13 | jsonData = json.loads(inputFile.read()) 14 | inputFile.close() 15 | 16 | return jsonData 17 | ``` 18 | 19 | This returns a `dict` made up of the json data. 20 | 21 | 22 | ### Writing JSON Data 23 | 24 | To transform a `dict` into a json string, then write it to an output file: 25 | 26 | ```py 27 | def writeJsonToFile(outputData: dict, fileName: str) -> None: 28 | jsonString = json.dumps(outputData, sort_keys=True, indent=4) 29 | 30 | with open(fileName, "w") as outputFile: 31 | outputFile.write(jsonString) 32 | ``` 33 | 34 | ## Transforming an object to a json string 35 | 36 | 37 | If you try to write an object to json using the method above, you will get an error. To fix this, just add a simple part to `writeJsonToFile()`: 38 | 39 | ```py 40 | def writeJsonToFile(outputData: dict, fileName: str) -> None: 41 | jsonString = json.dumps(object, indent=4, sort_keys=True, default=lambda o: getattr(o, '__dict__', str(o))) 42 | 43 | with open(fileName, "w") as outputFile: 44 | outputFile.write(jsonString) 45 | ``` 46 | -------------------------------------------------------------------------------- /entries/python-mysql-notes.md: -------------------------------------------------------------------------------- 1 | This is a short tutorial on how to connect to a MySQL database using python. 2 | 3 | ## Dependencies 4 | 5 | In order to connect to your database, you first need to install the [MySQL Connector Python](https://github.com/mysql/mysql-connector-python) by entering in the terminal: 6 | 7 | ```bash 8 | pip install mysql-connector-python 9 | ``` 10 | 11 | At the top of your script, you need to import the mysql module via: 12 | 13 | ```py 14 | import mysql.connector 15 | ``` 16 | 17 | ## Connecting to the database 18 | 19 | To initialize the connection, you need to use ```mysql.connector.connect()```: 20 | 21 | ```py 22 | # initialize database connection 23 | mydb = mysql.connector.connect( 24 | host = "host", 25 | user = "user", 26 | passwd = "password", 27 | database = "database" 28 | ) 29 | 30 | # connect to database 31 | mycursor = mydb.cursor() 32 | ``` 33 | 34 | ## Select Statement 35 | 36 | Here is an example case of a ```SELECT``` statement: 37 | 38 | ```py 39 | # retrieve all user id's, first names, and last names 40 | sql = "SELECT id, first_name, last_name from Users" 41 | mycursor.execute(sql) 42 | users = mycursor.fetchall() 43 | 44 | # print the results 45 | for user in users: 46 | print(user[0], user[1], user[2]) # id, first name, last name 47 | ``` 48 | 49 | ### Using parameters 50 | 51 | Using parameters in a statement is pretty straightforward. We are going to use the same example above, but only select the rows whose first name is 'Ryan' and last name is 'Smith': 52 | 53 | ```py 54 | # retrieve all user id's, first names, and last names 55 | # whose first name is Ryan and last name is Smith 56 | sql = "SELECT id, first_name, last_name FROM Users WHERE first_name = %s AND last_name = %s" 57 | parm_first_name = "Ryan" 58 | parm_last_name = "Smith" 59 | 60 | # bind parameters into a tuple 61 | parms = (parm_first_name, parm_last_name) 62 | 63 | # execute the statement 64 | mycursor.execute(sql, parms) 65 | users = mycursor.fetchall() 66 | 67 | # print the results 68 | for user in users: 69 | print(user[0], user[1], user[2]) # id, first name, last name 70 | ``` 71 | 72 | ## Insert Statement 73 | 74 | Here is an example of an ```INSERT``` statement: 75 | 76 | ```py 77 | sql = "INSERT INTO Users (first_name, last_name) VALUES (%s, %s)" 78 | 79 | # set parms 80 | first = "George" 81 | last = "Washington" 82 | parms = (first, last) 83 | 84 | # execute insert statement 85 | mycursor.execute(sql, val) 86 | mydb.commit() 87 | 88 | # print number of inserted records 89 | print(mycursor.rowcount, "record updated.") 90 | ``` 91 | -------------------------------------------------------------------------------- /entries/python-resources.md: -------------------------------------------------------------------------------- 1 | A collection of my personal favorite python modules and libraries. 2 | 3 | * [Beautiful Table](https://github.com/pri22296/beautifultable) — this Package provides BeautifulTable class for easily printing tabular data in a visually appealing ASCII format to a terminal 4 | * [MySQL Connector](https://github.com/mysql/mysql-connector-python) — enables Python programs to access MySQL databases 5 | * [Prompt Toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) — a library for building powerful interactive command line applications in Python 6 | * [python-markdown2](https://github.com/trentm/python-markdown2) — A fast and complete implementation of Markdown in Python -------------------------------------------------------------------------------- /entries/python-string-functions.md: -------------------------------------------------------------------------------- 1 | **Note:** All string methods returns new values. They do not change the original string. 2 | 3 | 4 | Method | Description 5 | :--- | :--- 6 | `capitalize()` | Converts the first character to upper case 7 | `casefold()` | Converts string into lower case 8 | `center()` | Returns a centered string 9 | `count()` | Returns the number of times a specified value occurs in a string 10 | `encode()` | Returns an encoded version of the string 11 | `endswith()` | Returns true if the string ends with the specified value 12 | `expandtabs()` | Sets the tab size of the string 13 | `find()` | Searches the string for a specified value and returns the position of where it was found 14 | `format()` | Formats specified values in a string 15 | `format_map()` | Formats specified values in a string 16 | `index()` | Searches the string for a specified value and returns the position of where it was found 17 | `isalnum()` | Returns True if all characters in the string are alphanumeric 18 | `isalpha()` | Returns True if all characters in the string are in the alphabet 19 | `isdecimal()` | Returns True if all characters in the string are decimals 20 | `isdigit()` | Returns True if all characters in the string are digits 21 | `isidentifier()` | Returns True if the string is an identifier 22 | `islower()` | Returns True if all characters in the string are lower case 23 | `isnumeric()` | Returns True if all characters in the string are numeric 24 | `isprintable()` | Returns True if all characters in the string are printable 25 | `isspace()` | Returns True if all characters in the string are whitespaces 26 | `istitle()` | Returns True if the string follows the rules of a title 27 | `isupper()` | Returns True if all characters in the string are upper case 28 | `join()` | Joins the elements of an iterable to the end of the string 29 | `ljust()` | Returns a left justified version of the string 30 | `lower()` | Converts a string into lower case 31 | `lstrip()` | Returns a left trim version of the string 32 | `maketrans()` | Returns a translation table to be used in translations 33 | `partition()` | Returns a tuple where the string is parted into three parts 34 | `replace()` | Returns a string where a specified value is replaced with a specified value 35 | `rfind()` | Searches the string for a specified value and returns the last position of where it was found 36 | `rindex()` | Searches the string for a specified value and returns the last position of where it was found 37 | `rjust()` | Returns a right justified version of the string 38 | `rpartition()` | Returns a tuple where the string is parted into three parts 39 | `rsplit()` | Splits the string at the specified separator, and returns a list 40 | `rstrip()` | Returns a right trim version of the string 41 | `split()` | Splits the string at the specified separator, and returns a list 42 | `splitlines()` | Splits the string at line breaks and returns a list 43 | `startswith()` | Returns true if the string starts with the specified value 44 | `strip()` | Returns a trimmed version of the string 45 | `swapcase()` | Swaps cases, lower case becomes upper case and vice versa 46 | `title()` | Converts the first character of each word to upper case 47 | `translate()` | Returns a translated string 48 | `upper()` | Converts a string into upper case 49 | `zfill()` | Fills the string with a specified number of 0 values at the beginning 50 | 51 |
52 | 53 | Source: https://www.w3schools.com/python/python_ref_string.asp 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /entries/regex-cheatsheet.md: -------------------------------------------------------------------------------- 1 | 2 | This is going to be a running of list of some regex examples for future reference. I like to use Dave Child's [Regular Expressions Cheat Sheet](https://cheatography.com/davechild/cheat-sheets/regular-expressions/pdf/) if I need some syntax help. 3 | 4 | 5 | Expression | Result 6 | :--- | :--- 7 | `\.log\b` | All strings ending in *.log* -------------------------------------------------------------------------------- /entries/rollup.js-configuration-file.md: -------------------------------------------------------------------------------- 1 | This is the `rollup.config.js` file that is needed to use [rollup.js](https://rollupjs.org/guide/en/): 2 | 3 | ```js 4 | class RollupConfig 5 | { 6 | constructor(input, output) { 7 | this.input = input; 8 | 9 | this.output = { 10 | format: 'iife', 11 | compact: true, 12 | sourcemap: true, 13 | file: output, 14 | } 15 | } 16 | } 17 | 18 | 19 | // add each entry-point file and its output here: 20 | const configs = [ 21 | new RollupConfig('custom/pages/new-watch/index.js', 'dist/new-watch.bundle.js'), 22 | ]; 23 | 24 | 25 | // rollup.config.js 26 | export default configs; 27 | ``` 28 | 29 | Then, in the terminal: 30 | 31 | ```sh 32 | rollup --config rollup.config.js --watch 33 | ``` -------------------------------------------------------------------------------- /entries/searching-a-table-with-javascript.md: -------------------------------------------------------------------------------- 1 | This is an example of how to search for values within an HTML table by using the [HTML Table Search](https://github.com/rrickgauer/html-table-search-js) library. I forked the searching JavaScript code from [here](https://github.com/niksofteng/html-table-search-js). A [demo page](https://niksofteng.github.io/html-table-search-js/) is provided by the original author. 2 | 3 | ## Requirements 4 | 5 | Be sure to download and include the JavaScript [source file](https://raw.githubusercontent.com/rrickgauer/html-table-search-js/master/tablesearch.js). 6 | 7 | ## Example 8 | 9 | Say you have in your ```index.html``` file the following table: 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
idfirst_namelast_nameemaildateamount
1LionelloCavanaghlcavanagh0@theglobeandmail.com1/29/2020176.18
2EmmiGillegill1@yellowbook.com4/23/2020192.69
3HeathSurmeyerhsurmeyer2@samsung.com8/18/2019241.93
54 | ``` 55 | 56 | The tablesearch arguments are as follows: 57 | 58 | ```js 59 | new TableSearch(searchBoxId, dataTableId [, options]).init(); 60 | ``` 61 | 62 | For the example, you could use the following code to activate the table search: 63 | 64 | ```js 65 | new TableSearch('table-search-input', 'mytable').init(); 66 | ``` 67 | 68 | At the bottom of your html code be sure to include the JavaScript files: 69 | 70 | ```html 71 | 72 | 73 | 74 | 77 | ``` 78 | -------------------------------------------------------------------------------- /entries/software-development-tools.md: -------------------------------------------------------------------------------- 1 | This is a list of my favorite software tools I use when developing applications. 2 | 3 | ## Text Editors & IDEs: 4 | 5 | ### Sublime Text 6 | 7 | I use [sublime text](https://www.sublimetext.com/) for several different languages. All my web development is written with sublime: html, css, php, and javascript. Also, I use it to write my python scripts. 8 | 9 | Before sublime, I used to use [Atom](https://atom.io/). However, after using it extensively, it just became too buggy and slow. Sublime opens instantly and has just about the same packages as atom. 10 | 11 | 12 | ### Visual Studio 13 | 14 | I use [Visual Studio](https://visualstudio.microsoft.com/) soley for C++. At my current job, that's one of the primary languages I work with so I am constantly using it. 15 | 16 | Before starting my job, I was writing my C++ code with [XCode](https://developer.apple.com/xcode/) on my macbook. Whenever I tried to use Visual Studio on my windows desktop, I couldn't get my programs to compile and run. Since starting my job though, I have come to love visual studio, and would reccommend it to anyone in C++ development. 17 | 18 | ### Netbeans 19 | 20 | I use [Netbeans](https://netbeans.org/) for my Java programs. It's free and I like it's swing form builder. The other popular option is [Eclipse](https://www.eclipse.org/downloads/packages/release/kepler/sr1/eclipse-ide-java-developers). 21 | 22 | ## Command Line Interface 23 | 24 | When I am writing code on my windows desktop, I use [terminus](https://eugeny.github.io/terminus/) for the command line instead of using the default CMD. It is open source, you can modify its apearance and have multiple tabs open at once. 25 | 26 | 27 | ## SQL Tools 28 | 29 | ### SQL Formatter 30 | 31 | When writing SQL queries, I like to use [SQL Formatter](http://www.dpriver.com/pp/sqlformat.htm) to format the layout of my text. For example, it can take this one line query: 32 | 33 | ```sql 34 | select Songs.id, Songs.title, Artists.name from Songs left join Artists on Songs.artist_id = Artists.id where Songs.id > 100 order by Songs.title desc limit 20; 35 | ``` 36 | 37 | and turn it into: 38 | 39 | ```sql 40 | SELECT Songs.id, 41 | Songs.title, 42 | Artists.name 43 | FROM Songs 44 | LEFT JOIN Artists 45 | ON Songs.artist_id = Artists.id 46 | WHERE Songs.id > 100 47 | ORDER BY Songs.name DESC 48 | LIMIT 20; 49 | ``` 50 | 51 | ### Mockaroo 52 | 53 | [Mockaroo](https://mockaroo.com/) is a realistic data generator. I use it to populate my database tables whenever with mock data whenever I am doing web development. It's free and really simple to use. 54 | 55 | ## Other tools 56 | 57 | ### Noteable 58 | 59 | [Noteable](https://notable.app/) is a free note taking app where you can write your notes in markdown. I use it for all my notetaking for work and I used it when I was in school. You can paste screen shots, enter in math equations, and so much more. I HIGHLY recommend this. 60 | 61 | ### Backlog 62 | 63 | [Backlog](http://www.backlog.cloud/) is a free, open source todo list app. It's really quick and fast, and if you are looking for a simple todo list app I recommend this. 64 | -------------------------------------------------------------------------------- /entries/sql-format.md: -------------------------------------------------------------------------------- 1 | Today I published a python application called [sql-format](https://github.com/rrickgauer/sql-format) It's a simple SQL statement formatter. 2 | 3 | ## Example 4 | 5 | Let's say you have the following SQL statement: 6 | 7 | ```sql 8 | select Songs.id, Songs.title, Artists.name from Songs left join Artists on Songs.artist_id = Artists.id where Songs.id > 100 order by Songs.title desc limit 20; 9 | ``` 10 | 11 | If you paste that into the textbox, its result would be: 12 | 13 | ```sql 14 | SELECT Songs.id, 15 | Songs.title, 16 | Artists.name 17 | FROM Songs 18 | LEFT JOIN Artists 19 | ON Songs.artist_id = Artists.id 20 | WHERE Songs.id > 100 21 | ORDER BY Songs.title DESC 22 | LIMIT 20; 23 | ``` 24 | -------------------------------------------------------------------------------- /entries/sublime-text-keyboard-shortcuts.md: -------------------------------------------------------------------------------- 1 | A complete list of shortcuts can be found [here](https://shortcuts.design/toolspage-sublimetext.html). 2 | 3 | 4 | Shortcut | Description 5 | :--- | :--- 6 | Ctrl+1 | Fold all code 7 | Ctrl+Alt+A | Align code 8 | Ctrl+F2 | Toggle Bookmark -------------------------------------------------------------------------------- /entries/test-delete.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |
8 | 9 | 10 | 11 |
12 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /entries/web-application-template.md: -------------------------------------------------------------------------------- 1 | I created a [github repo](https://github.com/rrickgauer/Web-Application-Template) that serves as a template I use for starting many of my php web applications. 2 | 3 | All you have to do is [click this link](https://github.com/rrickgauer/Web-Application-Template/archive/master.zip) to download the files. 4 | 5 | ## Additional steps 6 | 7 | * Add a db-info.php page 8 | * Create a subdirectory called *tables* and run my [tables application](https://github.com/rrickgauer/tables) -------------------------------------------------------------------------------- /entries/word-search-generator-project.md: -------------------------------------------------------------------------------- 1 | Recently, I purchased a subscription to the [Chicago Tribune](https://www.chicagotribune.com/), and while reading over the first issue I received, I noticed that they included a word search. As the days went on, I really started to enjoy working through the puzzle. 2 | 3 | This got me thinking about how the publishers generated the puzzle. So I decided to see if I could make my own. As a result, I am pleased to share my own [word search puzzle generator](https://ryanrickgauer.com/word-search/index.html)! Here is a link to the [GitHub repo](https://github.com/rrickgauer/word-search-generator). 4 | 5 | The premise behind the program is simple: have users enter in a list of words, then generate a word search puzzle. 6 | 7 |

8 | 9 |

10 | 11 | ## Generating the puzzle 12 | 13 | To generate my puzzle, I had to setup a few things. 14 | 15 | 16 | ### List of words 17 | 18 | After the user enters the list of words they would like to use, the program sorts the words longest to shortest. 19 | 20 | ### Directions 21 | 22 | Words in a puzzle can have 8 *directions*: 23 | 24 | Direction | Visual 25 | --- | --- 26 | up | ↑ 27 | down | ↓ 28 | left | ← 29 | right | → 30 | up-left | ↖ 31 | up-right | ↗ 32 | down-left | ↙ 33 | down-right | ↘ 34 | 35 | 36 | ### Puzzle Grid 37 | 38 | Every time a puzzle is generated, you need to setup the initial puzzle grid. 39 | 40 | To do this, I created a 2-d array and set every point in there to a `NULL` value. 41 | 42 | #### Grid Points 43 | 44 | My programs implements a `Point` class that represents a (X, Y) point on the puzzle grid. 45 | 46 | ### Algorithm 47 | 48 | Once the directions are set and the words are sorted, it is time to generate the puzzle. 49 | 50 | The algorithm was pretty simple, for each word in the puzzle: 51 | 52 | 1. Assign a random direction to the word 53 | 2. Assign a random starting point to the word 54 | 3. Check if the word can be inserted into the grid with its direction and starting point 55 | * if Yes, insert the word into the grid 56 | * if No, go back to step 1 57 | 58 | ### How to check if word can be inserted 59 | 60 | To check if the word can be inserted into the puzzle, there are a few things you need to examine. You will need the word itself, the starting point, and the direction. 61 | 62 | The word *cannot* be inserted if any of the conditions are true: 63 | 64 | * If the word length exceeds the grid dimensions 65 | * The grid is 4 x 4 size and the word is "COLLEGE". 66 | * If the word "falls off" the grid due to the direction and number of points 67 | * For example, say the grid is a 10 x 10 size, the word is "CHICAGO", the starting point is (9, 10), and the direction is down. 68 | * After the first letter ("C") is placed, the next point would be (9, 11) which does not exist in the grid 69 | * If you reach a point in the grid that already has a letter placed in it, that does not equal the letter in the word to be placed 70 | 71 | ## Further reading 72 | 73 | https://github.com/sbj42/word-search-generator 74 | 75 | https://github.com/jamis/wordsearch 76 | 77 | https://github.com/danbarbarito/Word-Search-Generator 78 | 79 | https://weblog.jamisbuck.org/2015/9/26/generating-word-search-puzzles.html -------------------------------------------------------------------------------- /scripts/dev/start-css.bat: -------------------------------------------------------------------------------- 1 | :: -------------------------------------------- 2 | :: Start up the sass compiler 3 | :: -------------------------------------------- 4 | 5 | @REM cd C:\xampp\htdocs\files\blog\src\blog\static\css 6 | @REM sass --watch style.scss style.css 7 | 8 | 9 | cd C:\xampp\htdocs\files\blog\src\gui\Blog\Blog.Gui\wwwroot\css 10 | 11 | sass --watch custom/style.scss:dist/style.css 12 | 13 | -------------------------------------------------------------------------------- /scripts/dev/start-dev.bat: -------------------------------------------------------------------------------- 1 | :: -------------------------------------------- 2 | :: Startup all the development servers/compilers 3 | :: -------------------------------------------- 4 | 5 | cd C:\xampp\htdocs\files\blog\scripts\dev 6 | 7 | wt -M start-flask-server.bat ; start-css.bat -------------------------------------------------------------------------------- /scripts/dev/start-flask-server.bat: -------------------------------------------------------------------------------- 1 | :: -------------------------------------------- 2 | :: Start up the api development flask server 3 | :: -------------------------------------------- 4 | 5 | cd C:\xampp\htdocs\files\blog\src 6 | 7 | set flask_program=C:\xampp\htdocs\files\blog\src\.venv\Scripts\flask 8 | set FLASK_ENV=development 9 | set FLASK_APP=blog 10 | 11 | %flask_program% run --with-threads --host 0.0.0.0 --port 5050 -------------------------------------------------------------------------------- /scripts/dev/tables.bat: -------------------------------------------------------------------------------- 1 | 2 | cd "C:\xampp\htdocs\files\blog" 3 | 4 | tables view --name blog --all --output C:\xampp\htdocs\files\blog\.tables.output.txt 5 | 6 | start notepad ".tables.output.txt" 7 | 8 | exit 9 | -------------------------------------------------------------------------------- /scripts/vps/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /var/www/blog/scripts/vps 4 | 5 | printf "\n\n\n" 6 | echo "------------------------------------------------" 7 | echo 'Pulling down latest code from GitHub...' 8 | echo "------------------------------------------------" 9 | printf "\n\n\n" 10 | 11 | git pull 12 | 13 | 14 | printf "\n\n\n" 15 | echo "------------------------------------------------" 16 | echo 'Building dotnet Project' 17 | echo "------------------------------------------------" 18 | printf "\n\n\n" 19 | 20 | 21 | cd /var/www/blog/src/gui/Blog 22 | dotnet publish Blog.Gui -r linux-x64 --self-contained false -c Release 23 | 24 | 25 | 26 | printf "\n\n\n" 27 | echo "------------------------------------------------" 28 | echo 'Compiling css' 29 | echo "------------------------------------------------" 30 | printf "\n\n\n" 31 | 32 | cd /var/www/blog/src/gui/Blog/Blog.Gui/wwwroot/css 33 | sass --quiet --no-source-map custom/style.scss dist/style.css 34 | 35 | 36 | printf "\n\n\n" 37 | echo "------------------------------------------------" 38 | echo 'Starting up gui server...' 39 | echo "------------------------------------------------" 40 | printf "\n\n\n" 41 | 42 | cd /etc/systemd/system 43 | systemctl daemon-reload 44 | systemctl reload-or-restart blog.gui.service 45 | service apache2 restart 46 | -------------------------------------------------------------------------------- /scripts/vps/view-log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | journalctl -fu blog.gui.service --no-tail -------------------------------------------------------------------------------- /sql/tables/Entries.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `Entries` ( 2 | `id` int unsigned NOT NULL AUTO_INCREMENT, 3 | `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 4 | `title` varchar(100) NOT NULL, 5 | `link` varchar(200) NOT NULL, 6 | `file_name` varchar(150) DEFAULT NULL, 7 | `topic_id` int unsigned NOT NULL, 8 | PRIMARY KEY (`id`), 9 | UNIQUE KEY `id` (`id`), 10 | KEY `topic_id` (`topic_id`), 11 | CONSTRAINT `Entries_ibfk_1` FOREIGN KEY (`topic_id`) REFERENCES `Topics` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE 12 | ) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 13 | -------------------------------------------------------------------------------- /sql/tables/Topics.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Topics ( 2 | id INT UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT, 3 | name CHAR(250) NOT NULL UNIQUE, 4 | PRIMARY KEY (id) 5 | ) engine=innodb; -------------------------------------------------------------------------------- /sql/tables/Users.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Users ( 2 | id INT UNSIGNED NOT NULL UNIQUE AUTO_INCREMENT, 3 | email CHAR(200) NOT NULL UNIQUE, 4 | password VARCHAR(250) NOT NULL, 5 | PRIMARY KEY (id) 6 | ) engine = innodb; -------------------------------------------------------------------------------- /sql/views/View_Entries.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW `View_Entries` AS 2 | SELECT 3 | `e`.`id` AS `id`, 4 | cast(`e`.`date` AS date) AS `date`, 5 | date_format(`e`.`date`, '%M %D, %Y') AS `date_formatted`, 6 | `e`.`title` AS `title`, 7 | `e`.`link` AS `source_link`, 8 | `e`.`file_name` AS `file_name`, 9 | `e`.`topic_id` AS `topic_id`, 10 | `t`.`name` AS `topic_name` 11 | FROM 12 | ( 13 | `Entries` `e` 14 | LEFT JOIN `Topics` `t` ON((`t`.`id` = `e`.`topic_id`)) 15 | ) 16 | GROUP BY 17 | `e`.`id`; -------------------------------------------------------------------------------- /sql/views/View_Used_Topics.sql: -------------------------------------------------------------------------------- 1 | CREATE view View_Used_Topics 2 | AS 3 | SELECT t.id AS id, 4 | t.name AS name, 5 | (SELECT COUNT(*) 6 | FROM Entries e 7 | WHERE e.topic_id = t.id) AS count 8 | FROM Topics t 9 | WHERE t.id IN (SELECT topic_id FROM Entries); 10 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Blog.Gui.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Blog.Gui.Models; 2 | using Blog.Service.Services.Contracts; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Blog.Gui.Controllers; 7 | 8 | [Controller] 9 | [Route("")] 10 | public class HomeController(IEntryService entryService, ITopicService topicService, IMarkdownService markdownService) : Controller 11 | { 12 | private readonly IEntryService _entryService = entryService; 13 | private readonly ITopicService _topicService = topicService; 14 | private readonly IMarkdownService _markdownService = markdownService; 15 | 16 | 17 | /// 18 | /// GET: / 19 | /// GET: /entries 20 | /// 21 | /// 22 | [HttpGet] 23 | [HttpGet("entries")] 24 | public async Task ShowAllEntriesAsync() 25 | { 26 | var entryViews = await _entryService.GetAllEntriesAsync(); 27 | var usedTopics = await _topicService.GetUsedTopicsAsync(); 28 | 29 | EntriesViewModel viewModel = new() 30 | { 31 | Entries = entryViews.ToList(), 32 | UsedTopics = usedTopics.ToList(), 33 | }; 34 | 35 | return View("Index", viewModel); 36 | } 37 | 38 | 39 | /// 40 | /// GET: /entries/:entryId 41 | /// 42 | /// 43 | /// 44 | [HttpGet("entries/{entryId}")] 45 | public async Task ShowEntryAsync([FromRoute] uint entryId) 46 | { 47 | var entry = await _entryService.GetEntryAsync(entryId); 48 | 49 | if (entry == null) 50 | { 51 | return NotFound(); 52 | } 53 | 54 | var markdown = _markdownService.GetEntryHtml(entry.FileName!); 55 | 56 | EntryViewModel viewModel = new() 57 | { 58 | Entry = entry, 59 | Content = markdown, 60 | }; 61 | 62 | return View("Entry", viewModel); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Models/EntriesViewModel.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.TableView; 3 | 4 | namespace Blog.Gui.Models; 5 | 6 | public class EntriesViewModel 7 | { 8 | public List Entries { get; set; } = new(); 9 | public List UsedTopics { get; set; } = new(); 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Models/EntryViewModel.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.TableView; 2 | 3 | namespace Blog.Gui.Models; 4 | 5 | public class EntryViewModel 6 | { 7 | public EntryTableView Entry { get; set; } = new(); 8 | public string Content { get; set; } = string.Empty; 9 | } 10 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Gui.Models 2 | { 3 | public class ErrorViewModel 4 | { 5 | public string? RequestId { get; set; } 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 8 | } 9 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Other/WebGuiDependencyService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Services.Implementations; 2 | 3 | namespace Blog.Gui.Other; 4 | 5 | public class WebGuiDependencyService : DependencyService 6 | { 7 | public WebGuiDependencyService(bool isDevelopment, IServiceCollection services) : base(isDevelopment) 8 | { 9 | _serviceCollection = services; 10 | } 11 | 12 | protected override void InjectAdditionalDependencies(IServiceCollection services) 13 | { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Program.cs: -------------------------------------------------------------------------------- 1 | using Blog.Gui.Other; 2 | using Blog.Service.Domain.Configs; 3 | using Microsoft.AspNetCore.HttpOverrides; 4 | using Microsoft.Extensions.FileProviders; 5 | using System.Net; 6 | 7 | var builder = WebApplication.CreateBuilder(args); 8 | 9 | var isDevelopment = !builder.Environment.IsProduction(); 10 | WebGuiDependencyService serviceInjector = new(isDevelopment, builder.Services); 11 | 12 | serviceInjector.BuildServices(); 13 | 14 | builder.Services.AddControllersWithViews(); 15 | builder.Services.AddControllers(); 16 | builder.Services.AddHttpContextAccessor(); 17 | 18 | 19 | IConfigs config = isDevelopment ? new ConfigurationDev() : new ConfigurationProduction(); 20 | 21 | builder.Services.Configure(options => 22 | { 23 | options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; 24 | options.KnownProxies.Add(IPAddress.Parse(config.VpsIdAddress)); 25 | }); 26 | 27 | 28 | var app = builder.Build(); 29 | 30 | app.UseHttpsRedirection(); 31 | app.UseStaticFiles(new StaticFileOptions 32 | { 33 | FileProvider = new PhysicalFileProvider(config.StaticFilesPath), 34 | }); 35 | 36 | app.UseRouting(); 37 | app.UseAuthorization(); 38 | app.UseAuthentication(); 39 | app.MapControllers(); 40 | app.UseForwardedHeaders(); 41 | 42 | 43 | 44 | app.Run(); -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "http": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "applicationUrl": "http://localhost:5009" 11 | }, 12 | "https": { 13 | "commandName": "Project", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | }, 18 | "dotnetRunMessages": true, 19 | "applicationUrl": "https://localhost:7199;http://localhost:5009" 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | }, 29 | "iisSettings": { 30 | "windowsAuthentication": false, 31 | "anonymousAuthentication": true, 32 | "iisExpress": { 33 | "applicationUrl": "http://localhost:44084", 34 | "sslPort": 44317 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/Home/Entry.cshtml: -------------------------------------------------------------------------------- 1 | @model Blog.Gui.Models.EntryViewModel 2 | 3 | 4 | 5 | 6 | 7 | @Model.Entry?.Title 8 | 9 | 10 | 11 |
12 | 13 | @*Breadcrumb*@ 14 | 20 | 21 | @*Entry title and date*@ 22 |

@Model.Entry?.Title

23 | 24 | 25 | @*Entry Content*@ 26 |
27 | @Html.Raw(Model.Content) 28 |
29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model Blog.Gui.Models.EntriesViewModel 2 | 3 | 4 | 5 | 6 | 7 | Blog 8 | 9 | 10 | 11 |
12 |

Ryan Rickgauer's Blog

13 | 14 |
15 | 16 |
17 | Topics: 18 |
19 | 28 |
29 |
30 | 31 | 32 |
33 | Sort: 34 |
35 | 39 |
40 |
41 |
42 | 43 | 44 |
    45 | 46 | @foreach(var entry in Model.Entries) 47 | { 48 |
  • 49 |
    50 | @entry.Title 51 |
    @entry.TopicName
    52 |
    53 |
    @entry.Date?.ToString("MM/dd/yyyy")
    54 |
  • 55 | } 56 |
57 | 58 | 59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/Includes/_Copyright.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 |

4 | 5 |

6 |

7 | © @DateTime.Now.Year by 8 | Ryan Rickgauer 9 |

-------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/Includes/_Footer.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/Includes/_Header.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Blog.Gui 2 | @using Blog.Gui.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/css/custom/style.scss: -------------------------------------------------------------------------------- 1 | body { 2 | /*background-color: whitesmoke !important;*/ 3 | margin-bottom: 50px; 4 | font-size: 1rem; 5 | } 6 | 7 | .entry { 8 | display: flex; 9 | justify-content: space-between; 10 | background-color: inherit; 11 | } 12 | 13 | .entry .title { 14 | font-weight: 700; 15 | } 16 | 17 | .entry .date { 18 | opacity: 0.8; 19 | } 20 | 21 | .custom-font { 22 | font-family: 'Special Elite', cursive; 23 | } 24 | 25 | 26 | 27 | h1.custom-font { 28 | font-size: 40px; 29 | text-align: center; 30 | border: none; 31 | margin: 0; 32 | padding: 0; 33 | } 34 | 35 | #hero { 36 | font-family: 'Special Elite', cursive; 37 | font-size: 64px; 38 | border: none; 39 | padding: 0; 40 | margin: 0; 41 | text-align: center; 42 | } 43 | 44 | table { 45 | table-layout: auto; 46 | width: 100% !important; 47 | } 48 | 49 | .toolbar-select { 50 | display: flex; 51 | align-items: center; 52 | justify-content: flex-start; 53 | margin-bottom: 20px; 54 | margin-left: 10px; 55 | 56 | .label { 57 | margin-right: 10px; 58 | } 59 | 60 | .select-sort { 61 | padding-right: 50px; 62 | } 63 | } 64 | 65 | @media screen and (max-width: 700px) { 66 | 67 | .toolbar-select { 68 | display: block; 69 | } 70 | 71 | 72 | h1 { 73 | font-size: 36px !important; 74 | } 75 | 76 | .entry { 77 | flex-direction: column; 78 | } 79 | 80 | .post-title { 81 | font-size: 30px !important; 82 | } 83 | 84 | .container, 85 | body { 86 | padding: 0; 87 | } 88 | 89 | body { 90 | margin: 8px; 91 | margin-top: 20px; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrickgauer/blog/db2d609b59c22571d685ce83c436552a69ad80fc/src/gui/Blog/Blog.Gui/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrickgauer/blog/db2d609b59c22571d685ce83c436552a69ad80fc/src/gui/Blog/Blog.Gui/wwwroot/img/icon.png -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/js/custom/entry.js: -------------------------------------------------------------------------------- 1 |  2 | 3 | const fontClass = '.custom-font'; 4 | 5 | var typeit = new TypeIt(fontClass, { 6 | speed: 50, 7 | startDelay: 900 8 | }) 9 | .options({ 10 | speed: 50 11 | }); 12 | 13 | 14 | typeit.go(); -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/js/custom/home.js: -------------------------------------------------------------------------------- 1 | 2 | const SORT_OPTIONS = { 3 | DATE: "date", 4 | TITLE: "title", 5 | }; 6 | 7 | 8 | ////////// 9 | // main // 10 | ////////// 11 | $(document).ready(function() { 12 | addMyListeners(); 13 | initTypeIt(); 14 | }); 15 | 16 | 17 | function addMyListeners() { 18 | $('.select-sort').on('change', sortEntries); 19 | $('.select-filter').on('change', filterEntries); 20 | } 21 | 22 | function initTypeIt() { 23 | new TypeIt('.custom-font', { 24 | speed: 50, 25 | startDelay: 900 26 | }) 27 | .options({speed: 50}) 28 | .go(); 29 | } 30 | 31 | 32 | function sortEntries() { 33 | var sortOption = $('.select-sort').val(); 34 | 35 | if (sortOption == SORT_OPTIONS.DATE) 36 | sortEntriesByDate(); 37 | else 38 | sortEntriesByTitle(); 39 | } 40 | 41 | 42 | function sortEntriesByDate() { 43 | var entries = $('.entry'); 44 | 45 | entries.sort(function(a, b) { 46 | var dateA = $(a).attr('data-date'); 47 | var dateB = $(b).attr('data-date'); 48 | 49 | return (parseInt(dateA) > parseInt(dateB)) ? -1 : 1; 50 | }); 51 | 52 | $('.list-group').html(entries); 53 | } 54 | 55 | function sortEntriesByTitle() { 56 | var entries = $('.entry'); 57 | 58 | entries.sort(function(a, b) { 59 | var titleA = $(a).find('.title a').text().toUpperCase(); 60 | var titleB = $(b).find('.title a').text().toUpperCase(); 61 | 62 | return (titleA < titleB) ? -1 : 1; 63 | }); 64 | 65 | $('.list-group').html(entries); 66 | } 67 | 68 | function filterEntries() { 69 | const newFilterValue = $('.select-filter option:checked').val(); 70 | const entries = $('.entry'); 71 | 72 | if (newFilterValue == '_all') { 73 | $(entries).removeClass('d-none'); 74 | } else { 75 | $(entries).addClass('d-none'); 76 | $(`.entry[data-topic-id="${newFilterValue}"]`).removeClass('d-none'); 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/lib/js/tablesort/tablesort.date.js: -------------------------------------------------------------------------------- 1 | // Basic dates in dd/mm/yy or dd-mm-yy format. 2 | // Years can be 4 digits. Days and Months can be 1 or 2 digits. 3 | (function(){ 4 | var parseDate = function(date) { 5 | date = date.replace(/\-/g, '/'); 6 | date = date.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/, '$3-$2-$1'); // format before getTime 7 | 8 | return new Date(date).getTime() || -1; 9 | }; 10 | 11 | Tablesort.extend('date', function(item) { 12 | return ( 13 | item.search(/(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\.?\,?\s*/i) !== -1 || 14 | item.search(/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/) !== -1 || 15 | item.search(/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i) !== -1 16 | ) && !isNaN(parseDate(item)); 17 | }, function(a, b) { 18 | a = a.toLowerCase(); 19 | b = b.toLowerCase(); 20 | 21 | return parseDate(b) - parseDate(a); 22 | }); 23 | }()); 24 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/lib/js/tablesort/tablesort.dotstep.js: -------------------------------------------------------------------------------- 1 | // Dot separated values. E.g. IP addresses or version numbers. 2 | Tablesort.extend('dotsep', function(item) { 3 | return /^(\d+\.)+\d+$/.test(item); 4 | }, function(a, b) { 5 | a = a.split('.'); 6 | b = b.split('.'); 7 | 8 | for (var i = 0, len = a.length, ai, bi; i < len; i++) { 9 | ai = parseInt(a[i], 10); 10 | bi = parseInt(b[i], 10); 11 | 12 | if (ai === bi) continue; 13 | if (ai > bi) return -1; 14 | if (ai < bi) return 1; 15 | } 16 | 17 | return 0; 18 | }); 19 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Gui/wwwroot/lib/js/tablesort/tablesort.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * tablesort v5.2.1 (2020-06-02) 3 | * http://tristen.ca/tablesort/demo/ 4 | * Copyright (c) 2020 ; Licensed MIT 5 | */ 6 | !function(){function a(b,c){if(!(this instanceof a))return new a(b,c);if(!b||"TABLE"!==b.tagName)throw new Error("Element must be a table");this.init(b,c||{})}var b=[],c=function(a){var b;return window.CustomEvent&&"function"==typeof window.CustomEvent?b=new CustomEvent(a):(b=document.createEvent("CustomEvent"),b.initCustomEvent(a,!1,!1,void 0)),b},d=function(a){return a.getAttribute("data-sort")||a.textContent||a.innerText||""},e=function(a,b){return a=a.trim().toLowerCase(),b=b.trim().toLowerCase(),a===b?0:a0)if(a.tHead&&a.tHead.rows.length>0){for(e=0;e0&&n.push(m),o++;if(!n)return}for(o=0;o 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Configs/ConfigurationDev.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Configs; 2 | 3 | public class ConfigurationDev : ConfigurationProduction, IConfigs 4 | { 5 | public override bool IsProduction => false; 6 | 7 | //public override string DbDataBase => "blog_dev"; 8 | public override string DbDataBase => "blog"; 9 | 10 | public override string EntryFilesPath => @"C:\xampp\htdocs\files\blog\entries"; 11 | 12 | public override string StaticFilesPath => @"C:\xampp\htdocs\files\blog\src\gui\Blog\Blog.Gui\wwwroot"; 13 | } 14 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Configs/IConfigs.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Configs; 2 | 3 | public interface IConfigs 4 | { 5 | public bool IsProduction { get; } 6 | 7 | public string DbServer { get; } 8 | public string DbDataBase { get; } 9 | public string DbUser { get; } 10 | public string DbPassword { get; } 11 | 12 | public string VpsIdAddress { get; } 13 | 14 | public string EntryFilesPath { get; } 15 | 16 | public string StaticFilesPath { get; } 17 | 18 | public Uri GuiHttpAddress { get; } 19 | } 20 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Contracts/IModelForm.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Contracts; 2 | 3 | 4 | public class NewModelFormArgs 5 | { 6 | public required string Title { get; set; } 7 | 8 | public string SaveButtonText { get; set; } = "Save"; 9 | public string CancelButtonText { get; set; } = "Cancel"; 10 | 11 | public required Guid MessengerToken { get; set; } 12 | } 13 | 14 | public class EditModelFormArgs : NewModelFormArgs 15 | { 16 | public required T Model { get; set; } 17 | } 18 | 19 | 20 | public interface IModelForm where T : new() 21 | { 22 | public void EditModel(EditModelFormArgs args); 23 | public void NewModel(NewModelFormArgs args); 24 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Contracts/ITableView.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.CustomAttributes; 2 | using Blog.Service.Domain.Other; 3 | 4 | namespace Blog.Service.Domain.Contracts; 5 | 6 | public interface ITableView 7 | where TModel : new() 8 | where TTableView : ITableView 9 | { 10 | public static abstract explicit operator TModel(TTableView other); 11 | 12 | private static IEnumerable>> ViewProperties => PropertyAttribute>.GetAllPropertiesInClass(); 13 | 14 | public TModel CastToModel() 15 | { 16 | TModel result = new(); 17 | 18 | foreach (var property in ViewProperties) 19 | { 20 | var value = property.GetPropertyValueRaw(this); 21 | var modelPropertyName = property.Attribute.Name; 22 | result.GetType()?.GetProperty(modelPropertyName)?.SetValue(result, value); 23 | } 24 | 25 | return result; 26 | } 27 | } 28 | 29 | 30 | public static class TableViewExtensions 31 | { 32 | public static TModel CastToModel(this ITableView tableViewInterface) where TModel : new() where TTableView : ITableView 33 | { 34 | return tableViewInterface.CastToModel(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/CustomAttributes/AutoCopyPropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Blog.Service.Domain.CustomAttributes; 4 | 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class AutoCopyPropertyAttribute(string modelPropertyName, [CallerMemberName] string viewPropertyName = "") : Attribute 7 | { 8 | public string ModelPropertyName { get; } = modelPropertyName; 9 | public string ViewPropertyName { get; } = viewPropertyName; 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/CustomAttributes/CopyToPropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Blog.Service.Domain.CustomAttributes; 4 | 5 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] 6 | public class CopyToPropertyAttribute : Attribute 7 | { 8 | public Type DestinationType => typeof(T); 9 | public string Name { get; } 10 | 11 | public CopyToPropertyAttribute([CallerMemberName] string name="") 12 | { 13 | Name = name; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/CustomAttributes/SqlColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Blog.Service.Domain.CustomAttributes; 4 | 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class SqlColumnAttribute : Attribute 7 | { 8 | public string ColumnName { get; } 9 | 10 | public SqlColumnAttribute([CallerMemberName] string columnName="") 11 | { 12 | ColumnName = columnName; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Model/Entry.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Model; 2 | 3 | public class Entry 4 | { 5 | public int? Id { get; set; } 6 | public DateTime? Date { get; set; } = DateTime.Now; 7 | public string? Title { get; set; } 8 | public string? FileName { get; set; } 9 | public uint? TopicId { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Model/EntryTopic.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Model; 2 | 3 | public class EntryTopic 4 | { 5 | public uint? Id { get; set; } 6 | public string? Name { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Other/ClassPropertyAttributes.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Other; 2 | 3 | public class ClassPropertyAttributes where TAttribute : Attribute 4 | { 5 | public static Type AttributeType => typeof(TAttribute); 6 | 7 | public List> PropertyAttributes { get; private set; } 8 | public Type ClassType { get; private set; } 9 | 10 | public ClassPropertyAttributes(Type classType) 11 | { 12 | ClassType = classType; 13 | PropertyAttributes = PropertyAttribute.GetAllPropertiesInClass(ClassType).ToList(); 14 | } 15 | 16 | /// 17 | /// Get the assigned attribute for the specified property 18 | /// 19 | /// Name of the property 20 | /// 21 | public PropertyAttribute? Get(string propertyName) 22 | { 23 | return PropertyAttributes.Where(p => p.PropertyInfo.Name == propertyName).FirstOrDefault(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Other/InsertAutoRowResult.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Domain.Other; 2 | 3 | public class InsertAutoRowResult 4 | { 5 | public required int NumRows { get; set; } 6 | public required int RowId { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/Other/PropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using System.Reflection; 3 | 4 | namespace Blog.Service.Domain.Other; 5 | 6 | public sealed class PropertyAttribute where TAttribute : Attribute 7 | { 8 | public static Type AttributeType => typeof(TAttribute); 9 | 10 | public PropertyInfo PropertyInfo { get; private set; } 11 | public TAttribute Attribute { get; private set; } 12 | 13 | 14 | public PropertyAttribute(PropertyInfo propertyInfo, TAttribute attribute) 15 | { 16 | PropertyInfo = propertyInfo; 17 | Attribute = attribute; 18 | } 19 | 20 | public PropertyAttribute(PropertyInfo propertyInfo) 21 | { 22 | PropertyInfo = propertyInfo; 23 | Attribute = GetAttribute(propertyInfo); 24 | } 25 | 26 | /// 27 | /// Get the attribute for the specified property 28 | /// 29 | /// 30 | /// 31 | /// 32 | private static TAttribute GetAttribute(PropertyInfo propertyInfo) 33 | { 34 | var attr = propertyInfo.GetCustomAttribute(true); 35 | 36 | if (attr is null) 37 | { 38 | throw new NotSupportedException($"{propertyInfo.Name} does not have the custom attribute {nameof(TAttribute)}"); 39 | } 40 | 41 | return attr; 42 | } 43 | 44 | /// 45 | /// Get the property value as a nullable object 46 | /// 47 | /// 48 | /// 49 | public object? GetPropertyValueRaw(object? classData) 50 | { 51 | return GetPropertyValue(classData); 52 | } 53 | 54 | /// 55 | /// Get the property value 56 | /// 57 | /// 58 | /// 59 | /// 60 | public T? GetPropertyValue(object? classInstance) where T : class? 61 | { 62 | return PropertyInfo.GetValue(classInstance) as T; 63 | } 64 | 65 | 66 | public static IEnumerable> GetAllPropertiesInClass() 67 | { 68 | return GetAllPropertiesInClass(typeof(TClass)); 69 | } 70 | 71 | public static IEnumerable> GetAllPropertiesInClass(Type classType) 72 | { 73 | var classProperties = classType.GetProperties().Where(p => p.GetCustomAttribute() != null); 74 | 75 | var result = classProperties.Select(p => new PropertyAttribute(p)); 76 | 77 | return result; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/TableView/EntryTableView.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Configs; 2 | using Blog.Service.Domain.Contracts; 3 | using Blog.Service.Domain.CustomAttributes; 4 | using Blog.Service.Domain.Model; 5 | using System.Diagnostics; 6 | 7 | namespace Blog.Service.Domain.TableView; 8 | 9 | public class EntryTableView : ITableView, ITableView 10 | { 11 | 12 | [SqlColumn("id")] 13 | [CopyToProperty(nameof(Entry.Id))] 14 | public int? EntryId { get; set; } 15 | 16 | [SqlColumn("date")] 17 | [CopyToProperty(nameof(Entry.Date))] 18 | public DateTime? Date { get; set; } = DateTime.Now; 19 | 20 | [SqlColumn("title")] 21 | [CopyToProperty(nameof(Entry.Title))] 22 | public string? Title { get; set; } 23 | 24 | [SqlColumn("file_name")] 25 | [CopyToProperty(nameof(Entry.FileName))] 26 | public string? FileName { get; set; } 27 | 28 | [SqlColumn("topic_id")] 29 | [CopyToProperty(nameof(Entry.TopicId))] 30 | [CopyToProperty(nameof(EntryTopic.Id))] 31 | public uint? TopicId { get; set; } 32 | 33 | [SqlColumn("topic_name")] 34 | [CopyToProperty(nameof(EntryTopic.Name))] 35 | public string? TopicName { get; set; } 36 | 37 | public string WpfUiCardHeaderText => $"{Title} #{EntryId}"; 38 | 39 | public DateOnly? WpfDateDisplayText 40 | { 41 | get 42 | { 43 | if (!Date.HasValue) 44 | { 45 | return null; 46 | } 47 | 48 | return DateOnly.FromDateTime(Date.Value); 49 | } 50 | } 51 | 52 | public string DateSort => Date?.ToString("yyyyMMdd") ?? "0"; 53 | 54 | #region - Methods - 55 | 56 | public void ViewPublication(IConfigs configs) 57 | { 58 | string url = GetPublicUrl(configs); 59 | OpenFile(url); 60 | } 61 | 62 | public string GetPublicUrl(IConfigs configs) 63 | { 64 | ArgumentNullException.ThrowIfNull(EntryId); 65 | return $"{configs.GuiHttpAddress.AbsoluteUri}entries/{EntryId}"; 66 | } 67 | 68 | 69 | public void ViewMarkdownFile(IConfigs configs) 70 | { 71 | FileInfo markdownFile = GetMarkdownFileInfo(configs); 72 | 73 | OpenFile(markdownFile.FullName); 74 | } 75 | 76 | public FileInfo GetMarkdownFileInfo(IConfigs configs) 77 | { 78 | ArgumentNullException.ThrowIfNull(FileName); 79 | 80 | FileInfo markdownFile = new(Path.Combine(configs.EntryFilesPath, FileName)); 81 | 82 | return markdownFile; 83 | } 84 | 85 | private static void OpenFile(string filename) 86 | { 87 | ProcessStartInfo startInfo = new(filename) 88 | { 89 | UseShellExecute = true, 90 | }; 91 | 92 | Process.Start(startInfo); 93 | } 94 | 95 | #endregion 96 | 97 | 98 | #region - ITableView - 99 | 100 | public static explicit operator Entry(EntryTableView other) 101 | { 102 | return other.CastToModel(); 103 | } 104 | 105 | public static explicit operator EntryTopic(EntryTableView other) 106 | { 107 | return other.CastToModel(); 108 | } 109 | 110 | #endregion 111 | 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Domain/TableView/TopicTableView.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Contracts; 2 | using Blog.Service.Domain.CustomAttributes; 3 | using Blog.Service.Domain.Model; 4 | 5 | namespace Blog.Service.Domain.TableView; 6 | 7 | public class TopicTableView : ITableView 8 | { 9 | [SqlColumn("topic_id")] 10 | [CopyToProperty(nameof(EntryTopic.Id))] 11 | public uint? TopicId { get; set; } 12 | 13 | [SqlColumn("topic_name")] 14 | [CopyToProperty(nameof(EntryTopic.Name))] 15 | public string? Name { get; set; } 16 | 17 | [SqlColumn("count_entries")] 18 | public long? Count { get; set; } 19 | 20 | 21 | #region - ITableView - 22 | 23 | public static explicit operator EntryTopic(TopicTableView other) 24 | { 25 | return other.CastToModel(); 26 | } 27 | 28 | #endregion 29 | } 30 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Mappers/Tables/EntryTableViewMapper.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.TableView; 2 | using System.Data; 3 | 4 | namespace Blog.Service.Mappers.Tables; 5 | 6 | public class EntryTableViewMapper : TableMapper 7 | { 8 | public override EntryTableView ToModel(DataRow row) 9 | { 10 | EntryTableView result = new() 11 | { 12 | EntryId = Convert.ToInt32(row.Field(GetColumnName(nameof(EntryTableView.EntryId)))), 13 | Date = row.Field(GetColumnName(nameof(EntryTableView.Date))), 14 | Title = row.Field(GetColumnName(nameof(EntryTableView.Title))), 15 | TopicId = row.Field(GetColumnName(nameof(EntryTableView.TopicId))), 16 | TopicName = row.Field(GetColumnName(nameof(EntryTableView.TopicName))), 17 | FileName = row.Field(GetColumnName(nameof(EntryTableView.FileName))), 18 | }; 19 | 20 | return result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Mappers/Tables/TableMapper.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.CustomAttributes; 2 | using Blog.Service.Domain.Other; 3 | using System.Data; 4 | 5 | namespace Blog.Service.Mappers.Tables; 6 | 7 | public abstract class TableMapper 8 | { 9 | public abstract T ToModel(DataRow row); 10 | 11 | public static Type ModelType => typeof(T); 12 | protected static readonly ClassPropertyAttributes SqlColumnProperties = new(ModelType); 13 | 14 | public List ToModels(DataTable table) 15 | { 16 | List models = new(); 17 | 18 | foreach (DataRow row in table.AsEnumerable()) 19 | { 20 | var model = ToModel(row); 21 | models.Add(model); 22 | } 23 | 24 | return models; 25 | } 26 | 27 | protected static string GetColumnName(string propertyName) 28 | { 29 | var prop = SqlColumnProperties.Get(propertyName); 30 | 31 | if (prop == null) 32 | { 33 | throw new NotSupportedException($"{propertyName} is not a valid property in this class"); 34 | } 35 | 36 | return prop.Attribute.ColumnName; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Mappers/Tables/TopicTableViewMapper.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.TableView; 2 | using System.Data; 3 | 4 | namespace Blog.Service.Mappers.Tables; 5 | 6 | public class TopicTableViewMapper : TableMapper 7 | { 8 | public override TopicTableView ToModel(DataRow row) 9 | { 10 | TopicTableView result = new() 11 | { 12 | TopicId = row.Field(GetColumnName(nameof(TopicTableView.TopicId))), 13 | Name = row.Field(GetColumnName(nameof(TopicTableView.Name))), 14 | Count = row.Field(GetColumnName(nameof(TopicTableView.Count))), 15 | }; 16 | 17 | return result; 18 | } 19 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Commands/EntryCommands.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Repository.Commands; 2 | 3 | public sealed class EntryCommands 4 | { 5 | public const string SelectAll = @" 6 | SELECT 7 | ve.id AS id, 8 | ve.date AS date, 9 | ve.date_formatted AS date_formatted, 10 | ve.title AS title, 11 | ve.file_name AS file_name, 12 | ve.topic_id AS topic_id, 13 | ve.topic_name AS topic_name 14 | FROM 15 | View_Entries ve 16 | ORDER BY 17 | ve.date DESC;"; 18 | 19 | public const string Update = @" 20 | UPDATE 21 | Entries 22 | SET 23 | date = @date, 24 | title = @title, 25 | file_name = @file_name, 26 | topic_id = @topic_id 27 | WHERE 28 | id = @id;"; 29 | 30 | 31 | public const string Insert = @" 32 | INSERT INTO 33 | Entries (id, date, title, file_name, topic_id) 34 | VALUES 35 | (@id, @date, @title, @file_name, @topic_id);"; 36 | 37 | public const string Delete = @" 38 | DELETE FROM 39 | Entries 40 | WHERE 41 | id = @id;"; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Commands/TopicCommands.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.Service.Repository.Commands; 2 | 3 | public sealed class TopicCommands 4 | { 5 | public const string SelectAllUsed = @" 6 | SELECT 7 | * 8 | FROM 9 | View_Topics v 10 | WHERE 11 | v.count_entries > 0 12 | ORDER BY 13 | v.topic_name ASC;"; 14 | 15 | 16 | public const string SelectAll = @" 17 | SELECT 18 | * 19 | FROM 20 | View_Topics t 21 | ORDER BY 22 | t.topic_name ASC;"; 23 | 24 | 25 | public const string Insert = @" 26 | INSERT INTO 27 | Topics (id, name) 28 | VALUES 29 | (@id, @name);"; 30 | 31 | public const string Update = @" 32 | UPDATE 33 | Topics 34 | SET 35 | name = @name 36 | WHERE 37 | id = @id;"; 38 | 39 | public const string Delete = @" 40 | DELETE FROM 41 | Topics 42 | WHERE 43 | id = @id;"; 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Contracts/IEntryRepository.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.Other; 3 | using System.Data; 4 | 5 | namespace Blog.Service.Repository.Contracts; 6 | 7 | public interface IEntryRepository 8 | { 9 | public Task SelectAllAsync(); 10 | public Task InsertEntryAsync(Entry entry); 11 | public Task UpdateEntryAsync(Entry entry); 12 | public Task DeleteEntryAsync(int entryId); 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Contracts/ITopicRepository.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.Other; 3 | using System.Data; 4 | 5 | namespace Blog.Service.Repository.Contracts; 6 | 7 | public interface ITopicRepository 8 | { 9 | public Task SelectAllUsedAsync(); 10 | public Task SelectAllAsync(); 11 | public Task UpdateTopicAsync(EntryTopic topic); 12 | public Task InsertTopicAsync(EntryTopic topic); 13 | public Task DeleteTopicAsync(uint topicId); 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Implementations/EntryRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Blog.Service.Domain.Model; 3 | using Blog.Service.Domain.Other; 4 | using Blog.Service.Repository.Commands; 5 | using Blog.Service.Repository.Contracts; 6 | using Blog.Service.Repository.Other; 7 | using MySql.Data.MySqlClient; 8 | 9 | 10 | namespace Blog.Service.Repository.Implementations; 11 | 12 | public class EntryRepository(DatabaseConnection connection) : IEntryRepository 13 | { 14 | private readonly DatabaseConnection _connection = connection; 15 | 16 | public async Task SelectAllAsync() 17 | { 18 | MySqlCommand command = new(EntryCommands.SelectAll); 19 | 20 | var table = await _connection.FetchAllAsync(command); 21 | 22 | return table; 23 | } 24 | 25 | public async Task UpdateEntryAsync(Entry entry) 26 | { 27 | MySqlCommand command = new(EntryCommands.Update); 28 | 29 | AddEntryParmsToCommand(command, entry); 30 | 31 | return await _connection.ModifyAsync(command); 32 | } 33 | 34 | public async Task InsertEntryAsync(Entry entry) 35 | { 36 | MySqlCommand command = new(EntryCommands.Insert); 37 | 38 | AddEntryParmsToCommand(command, entry); 39 | 40 | return await _connection.InsertAsync(command); 41 | } 42 | 43 | private static void AddEntryParmsToCommand(MySqlCommand command, Entry entry) 44 | { 45 | command.Parameters.AddWithValue("@id", entry.Id); 46 | command.Parameters.AddWithValue("@date", entry.Date); 47 | command.Parameters.AddWithValue("@title", entry.Title); 48 | command.Parameters.AddWithValue("@file_name", entry.FileName); 49 | command.Parameters.AddWithValue("@topic_id", entry.TopicId); 50 | } 51 | 52 | public async Task DeleteEntryAsync(int entryId) 53 | { 54 | MySqlCommand command = new(EntryCommands.Delete); 55 | 56 | command.Parameters.AddWithValue("@id", entryId); 57 | 58 | return await _connection.ModifyAsync(command); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Implementations/TopicRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Blog.Service.Domain.Model; 3 | using Blog.Service.Domain.Other; 4 | using Blog.Service.Repository.Commands; 5 | using Blog.Service.Repository.Contracts; 6 | using Blog.Service.Repository.Other; 7 | using MySql.Data.MySqlClient; 8 | 9 | namespace Blog.Service.Repository.Implementations; 10 | 11 | public class TopicRepository(DatabaseConnection connection) : ITopicRepository 12 | { 13 | private readonly DatabaseConnection _connection = connection; 14 | 15 | public async Task SelectAllUsedAsync() 16 | { 17 | MySqlCommand command = new(TopicCommands.SelectAllUsed); 18 | 19 | var table = await _connection.FetchAllAsync(command); 20 | 21 | return table; 22 | } 23 | 24 | public async Task SelectAllAsync() 25 | { 26 | MySqlCommand command = new(TopicCommands.SelectAll); 27 | 28 | var table = await _connection.FetchAllAsync(command); 29 | 30 | return table; 31 | } 32 | 33 | public async Task UpdateTopicAsync(EntryTopic topic) 34 | { 35 | MySqlCommand command = new(TopicCommands.Update); 36 | 37 | AddModifyParms(topic, command); 38 | 39 | return await _connection.ModifyAsync(command); 40 | } 41 | 42 | public async Task InsertTopicAsync(EntryTopic topic) 43 | { 44 | MySqlCommand command = new(TopicCommands.Insert); 45 | 46 | AddModifyParms(topic, command); 47 | 48 | return await _connection.InsertAsync(command); 49 | } 50 | 51 | private void AddModifyParms(EntryTopic topic, MySqlCommand command) 52 | { 53 | command.Parameters.AddWithValue("@id", topic.Id); 54 | command.Parameters.AddWithValue("@name", topic.Name); 55 | } 56 | 57 | public async Task DeleteTopicAsync(uint topicId) 58 | { 59 | MySqlCommand command = new(TopicCommands.Delete); 60 | 61 | command.Parameters.AddWithValue("@id", topicId); 62 | 63 | return await _connection.ModifyAsync(command); 64 | } 65 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Repository/Other/RepositoryUtils.cs: -------------------------------------------------------------------------------- 1 | using MySql.Data.MySqlClient; 2 | using System.Data.Common; 3 | using System.Data; 4 | using Blog.Service.Domain.Configs; 5 | 6 | namespace Blog.Service.Repository.Other; 7 | 8 | public static class RepositoryUtils 9 | { 10 | public static async Task LoadDataTableAsync(MySqlCommand command) 11 | { 12 | DataTable dataTable = new(); 13 | 14 | DbDataReader reader = await command.ExecuteReaderAsync(); 15 | dataTable.Load(reader); 16 | 17 | return dataTable; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Contracts/IEntryService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.TableView; 3 | 4 | namespace Blog.Service.Services.Contracts; 5 | 6 | public interface IEntryService 7 | { 8 | public Task> GetAllEntriesAsync(); 9 | public Task GetEntryAsync(uint entryId); 10 | public Task GetEntryAsync(int entryId); 11 | public Task SaveEntryAsync(Entry entry); 12 | 13 | public Task DeleteEntryAsync(uint entryId); 14 | public Task DeleteEntryAsync(int entryId); 15 | } 16 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Contracts/IMarkdownService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Configs; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Blog.Service.Services.Contracts; 9 | 10 | public interface IMarkdownService 11 | { 12 | public string GetEntryHtml(string entryFileName); 13 | } 14 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Contracts/ITableMapperService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Mappers.Tables; 2 | using System.Data; 3 | 4 | namespace Blog.Service.Services.Contracts; 5 | 6 | public interface ITableMapperService 7 | { 8 | public T ToModel(DataRow dataRow); 9 | public List ToModels(DataTable dataTable); 10 | public TableMapper GetMapper(); 11 | } 12 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Contracts/ITopicService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.TableView; 3 | 4 | namespace Blog.Service.Services.Contracts; 5 | 6 | public interface ITopicService 7 | { 8 | public Task> GetUsedTopicsAsync(); 9 | public Task> GetAllTopicsAsync(); 10 | public Task SaveTopicAsync(EntryTopic topic); 11 | public Task DeleteTopicAsync(uint topicId); 12 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Implementations/DependencyService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Configs; 2 | using Blog.Service.Repository.Contracts; 3 | using Blog.Service.Repository.Implementations; 4 | using Blog.Service.Repository.Other; 5 | using Blog.Service.Services.Contracts; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Blog.Service.Services.Implementations; 9 | 10 | public abstract class DependencyService 11 | { 12 | protected abstract void InjectAdditionalDependencies(IServiceCollection services); 13 | 14 | public bool IsDevelopment { get; protected set; } = false; 15 | 16 | protected IServiceCollection _serviceCollection = new ServiceCollection(); 17 | 18 | public DependencyService(bool isDevelopment) 19 | { 20 | IsDevelopment = isDevelopment; 21 | } 22 | 23 | public virtual IServiceCollection BuildServices() 24 | { 25 | InjectConfig(); 26 | 27 | InjectEssentialServices(); 28 | 29 | InjectAdditionalDependencies(_serviceCollection); 30 | 31 | return _serviceCollection; 32 | } 33 | 34 | protected void InjectConfig() 35 | { 36 | // set the appropriate configuration class 37 | // depends if the app is running in development or production 38 | if (IsDevelopment) 39 | { 40 | _serviceCollection.AddSingleton(); 41 | } 42 | else 43 | { 44 | _serviceCollection.AddSingleton(); 45 | } 46 | } 47 | 48 | protected virtual void InjectEssentialServices() 49 | { 50 | _serviceCollection 51 | .AddScoped() 52 | .AddScoped() 53 | 54 | .AddSingleton() 55 | .AddSingleton() 56 | 57 | .AddScoped() 58 | .AddScoped() 59 | 60 | .AddTransient(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Implementations/EntryService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.TableView; 3 | using Blog.Service.Repository.Contracts; 4 | using Blog.Service.Services.Contracts; 5 | 6 | namespace Blog.Service.Services.Implementations; 7 | 8 | public class EntryService(IEntryRepository entryRepository, ITableMapperService tableMapperService) : IEntryService 9 | { 10 | private readonly IEntryRepository _entryRepository = entryRepository; 11 | private readonly ITableMapperService _tableMapperService = tableMapperService; 12 | 13 | public async Task> GetAllEntriesAsync() 14 | { 15 | var table = await _entryRepository.SelectAllAsync(); 16 | 17 | var entries = _tableMapperService.ToModels(table); 18 | 19 | return entries; 20 | } 21 | 22 | public async Task GetEntryAsync(uint entryId) 23 | { 24 | return await GetEntryAsync((int)entryId); 25 | } 26 | 27 | public async Task GetEntryAsync(int entryId) 28 | { 29 | var entries = await GetAllEntriesAsync(); 30 | 31 | var entry = entries.Where(e => e.EntryId == entryId).FirstOrDefault(); 32 | 33 | return entry; 34 | } 35 | 36 | public async Task SaveEntryAsync(Entry entry) 37 | { 38 | int entryId = await SaveEntryToRepoAsync(entry); 39 | 40 | return await GetEntryAsync(entryId); 41 | } 42 | 43 | /// 44 | /// Call appropriate repo method either save or insert the given Entry 45 | /// 46 | /// 47 | /// The entry id 48 | private async Task SaveEntryToRepoAsync(Entry entry) 49 | { 50 | int entryId = 0; 51 | 52 | if (entry.Id.HasValue) 53 | { 54 | await _entryRepository.UpdateEntryAsync(entry); 55 | entryId = entry.Id.Value; 56 | } 57 | 58 | else 59 | { 60 | entryId = (await _entryRepository.InsertEntryAsync(entry)).RowId; 61 | } 62 | 63 | return entryId; 64 | } 65 | 66 | 67 | public async Task DeleteEntryAsync(uint entryId) 68 | { 69 | return await DeleteEntryAsync((int)entryId); 70 | } 71 | 72 | public async Task DeleteEntryAsync(int entryId) 73 | { 74 | return await _entryRepository.DeleteEntryAsync(entryId); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Implementations/MarkdownService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Configs; 2 | using Blog.Service.Services.Contracts; 3 | using Markdig; 4 | 5 | namespace Blog.Service.Services.Implementations; 6 | 7 | public class MarkdownService : IMarkdownService 8 | { 9 | private readonly IConfigs _configs; 10 | 11 | private static readonly MarkdownPipeline _markdownPipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); 12 | 13 | private string EntriesFolder => _configs.EntryFilesPath; 14 | 15 | public MarkdownService(IConfigs configs) 16 | { 17 | _configs = configs; 18 | } 19 | 20 | 21 | public string GetEntryHtml(string entryFileName) 22 | { 23 | var entryFile = GetEntryAbsolutePath(entryFileName); 24 | var fileContent = File.ReadAllText(entryFile.FullName); 25 | 26 | var result = Markdown.ToHtml(fileContent, _markdownPipeline); 27 | 28 | return result; 29 | } 30 | 31 | private FileInfo GetEntryAbsolutePath(string entryFileName) 32 | { 33 | var pathText = Path.Combine(EntriesFolder, entryFileName); 34 | 35 | FileInfo result = new(pathText); 36 | 37 | if (!result.Exists) 38 | { 39 | throw new FileNotFoundException(pathText); 40 | } 41 | 42 | return result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Implementations/TableMapperService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Mappers.Tables; 2 | using Blog.Service.Services.Contracts; 3 | using System.Data; 4 | using System.Reflection; 5 | 6 | namespace Blog.Service.Services.Implementations; 7 | 8 | public class TableMapperService : ITableMapperService 9 | { 10 | private static readonly List ModelMappers = GetAllModelMappers().ToList(); 11 | 12 | #region - Fetch all model mappers - 13 | 14 | /// 15 | /// Get a list of each model mapper instantiation 16 | /// 17 | /// 18 | private static IEnumerable GetAllModelMappers() 19 | { 20 | var subclassTypes = GetModelMapperSubclassTypes(); 21 | 22 | var modelMapperObjects = subclassTypes.Select(t => Activator.CreateInstance(t)); 23 | 24 | return modelMapperObjects.ToList(); 25 | } 26 | 27 | /// 28 | /// Get each type of model mapper 29 | /// 30 | /// 31 | private static IEnumerable GetModelMapperSubclassTypes() 32 | { 33 | Type modelMapperType = typeof(TableMapper<>); 34 | Assembly assembly = Assembly.GetExecutingAssembly(); // Or use the assembly containing the classes you want to inspect 35 | var subclasses = assembly.GetTypes().Where(t => IsSubclassOfGeneric(t, modelMapperType) && !t.IsAbstract); 36 | 37 | return subclasses; 38 | } 39 | 40 | /// 41 | /// Check if generic 42 | /// 43 | /// 44 | /// 45 | /// 46 | private static bool IsSubclassOfGeneric(Type current, Type genericBase) 47 | { 48 | do 49 | { 50 | if (current.IsGenericType && current.GetGenericTypeDefinition() == genericBase) 51 | { 52 | return true; 53 | } 54 | } 55 | while ((current = current.BaseType) != null); 56 | 57 | return false; 58 | 59 | } 60 | 61 | #endregion 62 | 63 | 64 | public T ToModel(DataRow dataRow) 65 | { 66 | return GetMapper().ToModel(dataRow); 67 | } 68 | 69 | public List ToModels(DataTable dataTable) 70 | { 71 | return GetMapper().ToModels(dataTable); 72 | } 73 | 74 | public TableMapper GetMapper() 75 | { 76 | TableMapper? correctMapper = null; 77 | 78 | foreach (var mapper in ModelMappers) 79 | { 80 | var castAttempt = mapper as TableMapper; 81 | 82 | if (castAttempt != null) 83 | { 84 | correctMapper = castAttempt; 85 | break; 86 | } 87 | } 88 | 89 | if (correctMapper == null) 90 | { 91 | throw new NotSupportedException($"Model type {typeof(T)} is not supported."); 92 | } 93 | 94 | return correctMapper; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.Service/Services/Implementations/TopicService.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Model; 2 | using Blog.Service.Domain.TableView; 3 | using Blog.Service.Repository.Contracts; 4 | using Blog.Service.Services.Contracts; 5 | 6 | namespace Blog.Service.Services.Implementations; 7 | 8 | public class TopicService(ITopicRepository topicRepository, ITableMapperService tableMapperService) : ITopicService 9 | { 10 | private readonly ITopicRepository _topicRepository = topicRepository; 11 | private readonly ITableMapperService _tableMapperService = tableMapperService; 12 | 13 | public async Task> GetUsedTopicsAsync() 14 | { 15 | var dataTable = await _topicRepository.SelectAllUsedAsync(); 16 | 17 | var usedTopics = _tableMapperService.ToModels(dataTable); 18 | 19 | return usedTopics; 20 | } 21 | 22 | public async Task> GetAllTopicsAsync() 23 | { 24 | var dataTable = await _topicRepository.SelectAllAsync(); 25 | 26 | var usedTopics = _tableMapperService.ToModels(dataTable); 27 | 28 | return usedTopics; 29 | } 30 | 31 | public async Task SaveTopicAsync(EntryTopic topic) 32 | { 33 | var topicId = await SaveTopicInRepoAsync(topic); 34 | 35 | return await GetTopicAsync(topicId); 36 | } 37 | 38 | 39 | private async Task SaveTopicInRepoAsync(EntryTopic topic) 40 | { 41 | if (topic.Id.HasValue) 42 | { 43 | await _topicRepository.UpdateTopicAsync(topic); 44 | return topic.Id.Value; 45 | } 46 | else 47 | { 48 | var insertResult = await _topicRepository.InsertTopicAsync(topic); 49 | return (uint)insertResult.RowId; 50 | } 51 | } 52 | 53 | 54 | 55 | private async Task GetTopicAsync(uint topicId) 56 | { 57 | var topics = await GetAllTopicsAsync(); 58 | 59 | return topics.First(t => t.TopicId == topicId); 60 | } 61 | 62 | 63 | public async Task DeleteTopicAsync(uint topicId) 64 | { 65 | var canDelete = await CanDeleteTopicAsync(topicId); 66 | 67 | if (canDelete) 68 | { 69 | await _topicRepository.DeleteTopicAsync(topicId); 70 | } 71 | 72 | return canDelete; 73 | } 74 | 75 | private async Task CanDeleteTopicAsync(uint topicId) 76 | { 77 | var topic = await GetTopicAsync(topicId); 78 | 79 | long count = topic.Count ?? 0; 80 | 81 | if (count > 0) 82 | { 83 | return false; 84 | } 85 | 86 | return true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/App.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | False 24 | True 25 | 26 | 27 | 36 | 37 | 38 | 42 | 43 | 49 | 50 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using System.Windows; 7 | 8 | [assembly: ThemeInfo( 9 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 10 | //(used if a resource is not found in the page, 11 | // or application resource dictionaries) 12 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 13 | //(used if a resource is not found in the page, 14 | // app, or any theme specific resource dictionaries) 15 | )] 16 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Assets/wpfui-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrickgauer/blog/db2d609b59c22571d685ce83c436552a69ad80fc/src/gui/Blog/Blog.WpfGui/Assets/wpfui-icon-1024.png -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Assets/wpfui-icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrickgauer/blog/db2d609b59c22571d685ce83c436552a69ad80fc/src/gui/Blog/Blog.WpfGui/Assets/wpfui-icon-256.png -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Blog.WpfGui.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | app.manifest 7 | wpfui-icon.ico 8 | enable 9 | enable 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Converters/BoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Blog.WpfGui.Converters; 5 | 6 | public class BoolToVisibilityConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | var booleanVal = (bool)value; 11 | 12 | if (booleanVal) 13 | { 14 | return Visibility.Visible; 15 | } 16 | 17 | return Visibility.Collapsed; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Converters/BoolToVisibilityInverseConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace Blog.WpfGui.Converters; 5 | 6 | public class BoolToVisibilityInverseConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | var booleanVal = (bool)value; 11 | 12 | if (booleanVal) 13 | { 14 | return Visibility.Collapsed; 15 | } 16 | 17 | return Visibility.Visible; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Converters/EnumToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using System.Globalization; 7 | using System.Windows.Data; 8 | using Wpf.Ui.Appearance; 9 | 10 | namespace Blog.WpfGui.Converters; 11 | 12 | internal class EnumToBooleanConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (parameter is not string enumString) 17 | { 18 | throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); 19 | } 20 | 21 | if (!Enum.IsDefined(typeof(ApplicationTheme), value)) 22 | { 23 | throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum"); 24 | } 25 | 26 | var enumValue = Enum.Parse(typeof(ApplicationTheme), enumString); 27 | 28 | return enumValue.Equals(value); 29 | } 30 | 31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 32 | { 33 | if (parameter is not string enumString) 34 | { 35 | throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName"); 36 | } 37 | 38 | return Enum.Parse(typeof(ApplicationTheme), enumString); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Helpers/BindingProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Blog.WpfGui.Helpers; 8 | 9 | public class BindingProxy : Freezable 10 | { 11 | protected override Freezable CreateInstanceCore() 12 | { 13 | return new BindingProxy(); 14 | } 15 | 16 | public object Data 17 | { 18 | get { return (object)GetValue(DataProperty); } 19 | set { SetValue(DataProperty, value); } 20 | } 21 | 22 | public static readonly DependencyProperty DataProperty = 23 | DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Helpers/IMessengerHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Blog.WpfGui.Helpers; 2 | 3 | public interface IMessengerHandler 4 | { 5 | public void AddMessengerHandlers(); 6 | public void CleanupMessengerHandlers(); 7 | } 8 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Messenger/ViewMessages.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.TableView; 2 | using CommunityToolkit.Mvvm.Messaging.Messages; 3 | 4 | namespace Blog.WpfGui.Messenger; 5 | 6 | public class ViewMessages 7 | { 8 | public sealed class TestMessage(int id) : ValueChangedMessage(id) { } 9 | 10 | public sealed class EntryFormSavedMessage(EntryTableView entry) : ValueChangedMessage(entry) { } 11 | 12 | public sealed class TopicFormSavedMessage(TopicTableView topic) : ValueChangedMessage(topic) { } 13 | } 14 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Models/AppConfig.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | namespace Blog.WpfGui.Models 7 | { 8 | public class AppConfig 9 | { 10 | public string ConfigurationsFolder { get; set; } 11 | 12 | public string AppPropertiesFileName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Models/DataColor.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using System.Windows.Media; 7 | 8 | namespace Blog.WpfGui.Models 9 | { 10 | public struct DataColor 11 | { 12 | public Brush Color { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Resources/Translations.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | namespace Blog.WpfGui.Resources 7 | { 8 | public partial class Translations 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Services/ApplicationHostService.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Blog.WpfGui.Views.Pages; 7 | using Blog.WpfGui.Views.Windows; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Wpf.Ui; 11 | 12 | namespace Blog.WpfGui.Services 13 | { 14 | /// 15 | /// Managed host of the application. 16 | /// 17 | public class ApplicationHostService : IHostedService 18 | { 19 | private readonly IServiceProvider _serviceProvider; 20 | 21 | private INavigationWindow _navigationWindow; 22 | 23 | public ApplicationHostService(IServiceProvider serviceProvider) 24 | { 25 | _serviceProvider = serviceProvider; 26 | } 27 | 28 | /// 29 | /// Triggered when the application host is ready to start the service. 30 | /// 31 | /// Indicates that the start process has been aborted. 32 | public async Task StartAsync(CancellationToken cancellationToken) 33 | { 34 | await HandleActivationAsync(); 35 | } 36 | 37 | /// 38 | /// Triggered when the application host is performing a graceful shutdown. 39 | /// 40 | /// Indicates that the shutdown process should no longer be graceful. 41 | public async Task StopAsync(CancellationToken cancellationToken) 42 | { 43 | await Task.CompletedTask; 44 | } 45 | 46 | /// 47 | /// Creates main window during activation. 48 | /// 49 | private async Task HandleActivationAsync() 50 | { 51 | if (!Application.Current.Windows.OfType().Any()) 52 | { 53 | _navigationWindow = ( 54 | _serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow 55 | )!; 56 | _navigationWindow!.ShowWindow(); 57 | 58 | // home page 59 | _navigationWindow.Navigate(typeof(Views.Pages.LandingPage)); 60 | } 61 | 62 | await Task.CompletedTask; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Services/PageService.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Wpf.Ui; 7 | 8 | namespace Blog.WpfGui.Services 9 | { 10 | /// 11 | /// Service that provides pages for navigation. 12 | /// 13 | public class PageService : IPageService 14 | { 15 | /// 16 | /// Service which provides the instances of pages. 17 | /// 18 | private readonly IServiceProvider _serviceProvider; 19 | 20 | /// 21 | /// Creates new instance and attaches the . 22 | /// 23 | public PageService(IServiceProvider serviceProvider) 24 | { 25 | _serviceProvider = serviceProvider; 26 | } 27 | 28 | /// 29 | public T? GetPage() 30 | where T : class 31 | { 32 | if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T))) 33 | throw new InvalidOperationException("The page should be a WPF control."); 34 | 35 | return (T?)_serviceProvider.GetService(typeof(T)); 36 | } 37 | 38 | /// 39 | public FrameworkElement? GetPage(Type pageType) 40 | { 41 | if (!typeof(FrameworkElement).IsAssignableFrom(pageType)) 42 | throw new InvalidOperationException("The page should be a WPF control."); 43 | 44 | return _serviceProvider.GetService(pageType) as FrameworkElement; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Usings.cs: -------------------------------------------------------------------------------- 1 | global using CommunityToolkit.Mvvm.ComponentModel; 2 | global using CommunityToolkit.Mvvm.Input; 3 | global using System; 4 | global using System.Windows; 5 | global using NativeMessageBox = System.Windows.MessageBox; 6 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/ViewModels/Base/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.Messaging; 2 | using Wpf.Ui.Controls; 3 | 4 | namespace Blog.WpfGui.ViewModels.Base; 5 | 6 | public abstract partial class ViewModel : ObservableObject, INavigationAware 7 | { 8 | protected virtual Guid MessengerToken { get; } = Guid.NewGuid(); 9 | 10 | protected virtual void InitMessenger() 11 | { 12 | WeakReferenceMessenger.Default.RegisterAll(this, MessengerToken); 13 | } 14 | 15 | 16 | // INavigationAware 17 | public virtual void OnNavigatedFrom() 18 | { 19 | 20 | } 21 | 22 | public virtual void OnNavigatedTo() 23 | { 24 | 25 | } 26 | 27 | 28 | } -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/ViewModels/Pages/DataViewModel.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Blog.WpfGui.Models; 7 | using System.Windows.Media; 8 | using Wpf.Ui.Controls; 9 | 10 | namespace Blog.WpfGui.ViewModels.Pages 11 | { 12 | public partial class DataViewModel : ObservableObject, INavigationAware 13 | { 14 | private bool _isInitialized = false; 15 | 16 | [ObservableProperty] 17 | private IEnumerable _colors; 18 | 19 | public void OnNavigatedTo() 20 | { 21 | if (!_isInitialized) 22 | InitializeViewModel(); 23 | } 24 | 25 | public void OnNavigatedFrom() { } 26 | 27 | private void InitializeViewModel() 28 | { 29 | var random = new Random(); 30 | var colorCollection = new List(); 31 | 32 | for (int i = 0; i < 8192; i++) 33 | colorCollection.Add( 34 | new DataColor 35 | { 36 | Color = new SolidColorBrush( 37 | Color.FromArgb( 38 | (byte)200, 39 | (byte)random.Next(0, 250), 40 | (byte)random.Next(0, 250), 41 | (byte)random.Next(0, 250) 42 | ) 43 | ) 44 | } 45 | ); 46 | 47 | Colors = colorCollection; 48 | 49 | _isInitialized = true; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/ViewModels/Pages/LandingViewModel.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Configs; 2 | using Blog.WpfGui.ViewModels.Base; 3 | using Blog.WpfGui.Views.Pages; 4 | using System.Diagnostics; 5 | using Wpf.Ui; 6 | using Wpf.Ui.Controls; 7 | 8 | namespace Blog.WpfGui.ViewModels.Pages; 9 | 10 | public partial class LandingViewModel : ViewModel 11 | { 12 | private readonly INavigationView _navigation; 13 | private readonly IConfigs _configs; 14 | 15 | public LandingViewModel(INavigationService navigationService, IConfigs configs) 16 | { 17 | _navigation = navigationService.GetNavigationControl(); 18 | _configs = configs; 19 | } 20 | 21 | 22 | #region - Commands - 23 | 24 | [RelayCommand] 25 | private void EntriesPage() 26 | { 27 | _navigation.Navigate(typeof(EntriesPage)); 28 | } 29 | 30 | [RelayCommand] 31 | private void TopicsPage() 32 | { 33 | _navigation.Navigate(typeof(TopicsPage)); 34 | } 35 | 36 | [RelayCommand] 37 | private void BlogSite() 38 | { 39 | ProcessStartInfo startInfo = new(_configs.GuiHttpAddress.AbsoluteUri) 40 | { 41 | UseShellExecute = true, 42 | }; 43 | 44 | Process.Start(startInfo); 45 | } 46 | 47 | [RelayCommand] 48 | private void Documents() 49 | { 50 | ProcessStartInfo startInfo = new(_configs.EntryFilesPath) 51 | { 52 | UseShellExecute = true, 53 | }; 54 | 55 | Process.Start(startInfo); 56 | } 57 | 58 | [RelayCommand] 59 | private void Repository() 60 | { 61 | ProcessStartInfo startInfo = new(@"https://github.com/rrickgauer/blog") 62 | { 63 | UseShellExecute = true, 64 | }; 65 | 66 | Process.Start(startInfo); 67 | } 68 | 69 | #endregion 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/ViewModels/Pages/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Wpf.Ui.Appearance; 7 | using Wpf.Ui.Controls; 8 | 9 | namespace Blog.WpfGui.ViewModels.Pages 10 | { 11 | public partial class SettingsViewModel : ObservableObject, INavigationAware 12 | { 13 | private bool _isInitialized = false; 14 | 15 | [ObservableProperty] 16 | private string _appVersion = String.Empty; 17 | 18 | [ObservableProperty] 19 | private ApplicationTheme _currentTheme = ApplicationTheme.Unknown; 20 | 21 | public void OnNavigatedTo() 22 | { 23 | if (!_isInitialized) 24 | InitializeViewModel(); 25 | } 26 | 27 | public void OnNavigatedFrom() { } 28 | 29 | private void InitializeViewModel() 30 | { 31 | CurrentTheme = ApplicationThemeManager.GetAppTheme(); 32 | AppVersion = $"UiDesktopApp1 - {GetAssemblyVersion()}"; 33 | 34 | _isInitialized = true; 35 | } 36 | 37 | private string GetAssemblyVersion() 38 | { 39 | return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() 40 | ?? String.Empty; 41 | } 42 | 43 | [RelayCommand] 44 | private void OnChangeTheme(string parameter) 45 | { 46 | switch (parameter) 47 | { 48 | case "theme_light": 49 | if (CurrentTheme == ApplicationTheme.Light) 50 | break; 51 | 52 | ApplicationThemeManager.Apply(ApplicationTheme.Light); 53 | CurrentTheme = ApplicationTheme.Light; 54 | 55 | break; 56 | 57 | default: 58 | if (CurrentTheme == ApplicationTheme.Dark) 59 | break; 60 | 61 | ApplicationThemeManager.Apply(ApplicationTheme.Dark); 62 | CurrentTheme = ApplicationTheme.Dark; 63 | 64 | break; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/ViewModels/Pages/TopicFormViewModel.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.Contracts; 2 | using Blog.Service.Domain.TableView; 3 | using Blog.WpfGui.ViewModels.Base; 4 | using CommunityToolkit.Mvvm.Messaging; 5 | using Wpf.Ui; 6 | using Wpf.Ui.Controls; 7 | using static Blog.WpfGui.Messenger.ViewMessages; 8 | 9 | namespace Blog.WpfGui.ViewModels.Pages; 10 | 11 | public partial class TopicFormViewModel(INavigationService navigationService) : ViewModel, IModelForm 12 | { 13 | 14 | #region - Private Members - 15 | 16 | private readonly INavigationView _navigation = navigationService.GetNavigationControl(); 17 | 18 | private Guid _parentMessengerToken = Guid.Empty; 19 | 20 | private TopicTableView _topic = new(); 21 | 22 | protected TopicTableView Topic 23 | { 24 | get => _topic; 25 | set 26 | { 27 | _topic = value; 28 | NameInputText = _topic.Name ?? string.Empty; 29 | } 30 | } 31 | 32 | #endregion 33 | 34 | #region - Generated Properties - 35 | 36 | [ObservableProperty] 37 | private string _pageTitle = string.Empty; 38 | 39 | [ObservableProperty] 40 | private string _saveButtonText = string.Empty; 41 | 42 | [ObservableProperty] 43 | private string _cancelButtonText = string.Empty; 44 | 45 | [ObservableProperty] 46 | [NotifyCanExecuteChangedFor(nameof(SaveFormCommand))] 47 | private string _nameInputText = string.Empty; 48 | 49 | #endregion 50 | 51 | #region - IModelForm - 52 | 53 | public void EditModel(EditModelFormArgs args) 54 | { 55 | HandleNewModelFormArgs(args); 56 | Topic = args.Model; 57 | } 58 | 59 | public void NewModel(NewModelFormArgs args) 60 | { 61 | HandleNewModelFormArgs(args); 62 | Topic = new(); 63 | } 64 | 65 | #endregion 66 | 67 | #region - Commands - 68 | 69 | [RelayCommand(CanExecute = nameof(CanSaveForm))] 70 | private void SaveForm() 71 | { 72 | Topic.Name = NameInputText; 73 | 74 | WeakReferenceMessenger.Default.Send(new TopicFormSavedMessage(Topic), _parentMessengerToken); 75 | } 76 | 77 | private bool CanSaveForm() 78 | { 79 | if (string.IsNullOrWhiteSpace(NameInputText)) 80 | { 81 | return false; 82 | } 83 | 84 | return true; 85 | } 86 | 87 | [RelayCommand] 88 | private void CloseForm() 89 | { 90 | _navigation.GoBack(); 91 | } 92 | 93 | #endregion 94 | 95 | 96 | 97 | private void HandleNewModelFormArgs(NewModelFormArgs args) 98 | { 99 | PageTitle = args.Title; 100 | SaveButtonText = args.SaveButtonText; 101 | CancelButtonText = args.CancelButtonText; 102 | _parentMessengerToken = args.MessengerToken; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/ViewModels/Windows/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using System.Collections.ObjectModel; 7 | using Wpf.Ui.Controls; 8 | 9 | namespace Blog.WpfGui.ViewModels.Windows 10 | { 11 | public partial class MainWindowViewModel : ObservableObject 12 | { 13 | [ObservableProperty] 14 | private string _applicationTitle = "WPF UI - Blog.WpfGui"; 15 | 16 | [ObservableProperty] 17 | private ObservableCollection _menuItems = new() 18 | { 19 | new NavigationViewItem() 20 | { 21 | Content = "Home", 22 | Icon = new SymbolIcon { Symbol = SymbolRegular.Home24 }, 23 | TargetPageType = typeof(Views.Pages.LandingPage) 24 | }, 25 | 26 | new NavigationViewItem() 27 | { 28 | Content = "Entries", 29 | Icon = new SymbolIcon { Symbol = SymbolRegular.Document24 }, 30 | TargetPageType = typeof(Views.Pages.EntriesPage) 31 | }, 32 | 33 | new NavigationViewItem() 34 | { 35 | Content = "Topics", 36 | Icon = new SymbolIcon { Symbol = SymbolRegular.NumberSymbolSquare24 }, 37 | TargetPageType = typeof(Views.Pages.TopicsPage) 38 | }, 39 | }; 40 | 41 | [ObservableProperty] 42 | private ObservableCollection _footerMenuItems = new() 43 | { 44 | new NavigationViewItem() 45 | { 46 | Content = "Settings", 47 | Icon = new SymbolIcon { Symbol = SymbolRegular.Settings24 }, 48 | TargetPageType = typeof(Views.Pages.SettingsPage) 49 | } 50 | }; 51 | 52 | [ObservableProperty] 53 | private ObservableCollection _trayMenuItems = new() 54 | { 55 | new MenuItem { Header = "Home", Tag = "tray_home" } 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/DataPage.xaml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 25 | 26 | 27 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/DataPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Blog.WpfGui.ViewModels.Pages; 7 | using Wpf.Ui.Controls; 8 | 9 | namespace Blog.WpfGui.Views.Pages; 10 | 11 | public partial class DataPage : INavigableView 12 | { 13 | public DataViewModel ViewModel { get; } 14 | 15 | public DataPage(DataViewModel viewModel) 16 | { 17 | ViewModel = viewModel; 18 | DataContext = this; 19 | 20 | InitializeComponent(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/EntriesPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.TableView; 2 | using Blog.WpfGui.ViewModels.Pages; 3 | using Wpf.Ui.Controls; 4 | 5 | namespace Blog.WpfGui.Views.Pages; 6 | 7 | /// 8 | /// Interaction logic for EntriesPage.xaml 9 | /// 10 | public partial class EntriesPage : INavigableView 11 | { 12 | public EntriesViewModel ViewModel { get; } 13 | 14 | public EntriesPage(EntriesViewModel viewModel) 15 | { 16 | ViewModel = viewModel; 17 | DataContext = this; 18 | 19 | InitializeComponent(); 20 | 21 | 22 | } 23 | 24 | private void Row_DoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) 25 | { 26 | if (((FrameworkElement)sender).DataContext is EntryTableView entry) 27 | { 28 | ViewModel.OnEntryDoubleClicked(entry); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/EntryFormPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Blog.WpfGui.ViewModels.Pages; 2 | using Wpf.Ui.Controls; 3 | 4 | namespace Blog.WpfGui.Views.Pages; 5 | 6 | /// 7 | /// Interaction logic for EntryFormPage.xaml 8 | /// 9 | public partial class EntryFormPage : INavigableView 10 | { 11 | public EntryFormViewModel ViewModel { get; } 12 | 13 | public EntryFormPage(EntryFormViewModel viewModel) 14 | { 15 | ViewModel = viewModel; 16 | DataContext = this; 17 | 18 | InitializeComponent(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/LandingPage.xaml: -------------------------------------------------------------------------------- 1 |  18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 48 | 49 | 52 | 53 | 56 | 57 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/LandingPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Blog.WpfGui.ViewModels.Pages; 2 | using Wpf.Ui.Controls; 3 | 4 | namespace Blog.WpfGui.Views.Pages; 5 | 6 | /// 7 | /// Interaction logic for LandingPage.xaml 8 | /// 9 | public partial class LandingPage : INavigableView 10 | { 11 | public LandingViewModel ViewModel { get; } 12 | 13 | public LandingPage(LandingViewModel viewModel) 14 | { 15 | ViewModel = viewModel; 16 | DataContext = this; 17 | 18 | InitializeComponent(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/SettingsPage.xaml: -------------------------------------------------------------------------------- 1 |  18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 35 | 42 | 43 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/SettingsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Blog.WpfGui.ViewModels.Pages; 7 | using Wpf.Ui.Controls; 8 | 9 | namespace Blog.WpfGui.Views.Pages 10 | { 11 | public partial class SettingsPage : INavigableView 12 | { 13 | public SettingsViewModel ViewModel { get; } 14 | 15 | public SettingsPage(SettingsViewModel viewModel) 16 | { 17 | ViewModel = viewModel; 18 | DataContext = this; 19 | 20 | InitializeComponent(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/TopicFormPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Blog.WpfGui.ViewModels.Pages; 2 | using Wpf.Ui.Controls; 3 | 4 | namespace Blog.WpfGui.Views.Pages; 5 | 6 | /// 7 | /// Interaction logic for TopicFormPage.xaml 8 | /// 9 | public partial class TopicFormPage : INavigableView 10 | { 11 | public TopicFormViewModel ViewModel { get; } 12 | 13 | public TopicFormPage(TopicFormViewModel viewModel) 14 | { 15 | ViewModel = viewModel; 16 | DataContext = this; 17 | 18 | InitializeComponent(); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Pages/TopicsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Blog.Service.Domain.TableView; 2 | using Blog.WpfGui.ViewModels.Pages; 3 | using System.Windows.Input; 4 | using Wpf.Ui.Controls; 5 | 6 | namespace Blog.WpfGui.Views.Pages; 7 | 8 | /// 9 | /// Interaction logic for TopicsPage.xaml 10 | /// 11 | public partial class TopicsPage : INavigableView 12 | { 13 | public TopicsViewModel ViewModel { get; } 14 | 15 | public TopicsPage(TopicsViewModel viewModel) 16 | { 17 | ViewModel = viewModel; 18 | DataContext = this; 19 | 20 | InitializeComponent(); 21 | 22 | ViewModel.ScrollToItem += OnScrollToItem; 23 | } 24 | 25 | /// 26 | /// Scroll down to the given topic in the table 27 | /// 28 | /// 29 | /// 30 | private void OnScrollToItem(object? sender, TopicTableView e) 31 | { 32 | TopicsTable.ScrollIntoView(e); 33 | } 34 | 35 | private void DataGridCell_MouseDoubleClick(object sender, MouseButtonEventArgs e) 36 | { 37 | if (((FrameworkElement)sender).DataContext is TopicTableView topic) 38 | { 39 | ViewModel.OnRowSelected(topic); 40 | } 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/UserControls/PageTitleControl.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/UserControls/PageTitleControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace Blog.WpfGui.Views.UserControls; 4 | 5 | /// 6 | /// Interaction logic for PageTitleControl.xaml 7 | /// 8 | public partial class PageTitleControl : UserControl 9 | { 10 | 11 | public string Title 12 | { 13 | get => (string)GetValue(TitleProperty); 14 | set => SetValue(TitleProperty, value); 15 | } 16 | 17 | public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(PageTitleControl), new PropertyMetadata(string.Empty)); 18 | 19 | public PageTitleControl() 20 | { 21 | InitializeComponent(); 22 | RootControl.DataContext = this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Windows/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  23 | 24 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/Views/Windows/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the MIT License. 2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. 3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors. 4 | // All Rights Reserved. 5 | 6 | using Blog.WpfGui.ViewModels.Windows; 7 | using Wpf.Ui; 8 | using Wpf.Ui.Appearance; 9 | using Wpf.Ui.Controls; 10 | 11 | namespace Blog.WpfGui.Views.Windows; 12 | 13 | public partial class MainWindow : INavigationWindow 14 | { 15 | public MainWindowViewModel ViewModel { get; } 16 | 17 | public MainWindow( 18 | MainWindowViewModel viewModel, 19 | IPageService pageService, 20 | ISnackbarService snackbarService, 21 | INavigationService navigationService 22 | ) 23 | { 24 | ViewModel = viewModel; 25 | DataContext = this; 26 | 27 | SystemThemeWatcher.Watch(this); 28 | 29 | InitializeComponent(); 30 | SetPageService(pageService); 31 | 32 | snackbarService.SetSnackbarPresenter(SnackbarPresenter); 33 | 34 | navigationService.SetNavigationControl(RootNavigation); 35 | } 36 | 37 | #region INavigationWindow methods 38 | 39 | public INavigationView GetNavigation() => RootNavigation; 40 | 41 | public bool Navigate(Type pageType) => RootNavigation.Navigate(pageType); 42 | 43 | public void SetPageService(IPageService pageService) => RootNavigation.SetPageService(pageService); 44 | 45 | public void ShowWindow() => Show(); 46 | 47 | public void CloseWindow() => Close(); 48 | 49 | #endregion INavigationWindow methods 50 | 51 | /// 52 | /// Raises the closed event. 53 | /// 54 | protected override void OnClosed(EventArgs e) 55 | { 56 | base.OnClosed(e); 57 | 58 | // Make sure that closing this window will begin the process of closing the application. 59 | Application.Current.Shutdown(); 60 | } 61 | 62 | INavigationView INavigationWindow.GetNavigation() 63 | { 64 | throw new NotImplementedException(); 65 | } 66 | 67 | public void SetServiceProvider(IServiceProvider serviceProvider) 68 | { 69 | throw new NotImplementedException(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/gui/Blog/Blog.WpfGui/wpfui-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrickgauer/blog/db2d609b59c22571d685ce83c436552a69ad80fc/src/gui/Blog/Blog.WpfGui/wpfui-icon.ico -------------------------------------------------------------------------------- /src/gui/Blog/Blog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33815.320 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Gui", "Blog.Gui\Blog.Gui.csproj", "{B83493A2-98B5-439D-8B9F-AB5B503E6B27}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Service", "Blog.Service\Blog.Service.csproj", "{DDF8A190-C27B-4FAF-8654-AB40AAD71CEA}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blog.WpfGui", "Blog.WpfGui\Blog.WpfGui.csproj", "{D8467B14-270C-4F3E-B7B3-D2EAC499358E}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B83493A2-98B5-439D-8B9F-AB5B503E6B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {B83493A2-98B5-439D-8B9F-AB5B503E6B27}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {B83493A2-98B5-439D-8B9F-AB5B503E6B27}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {B83493A2-98B5-439D-8B9F-AB5B503E6B27}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {DDF8A190-C27B-4FAF-8654-AB40AAD71CEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {DDF8A190-C27B-4FAF-8654-AB40AAD71CEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {DDF8A190-C27B-4FAF-8654-AB40AAD71CEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {DDF8A190-C27B-4FAF-8654-AB40AAD71CEA}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {D8467B14-270C-4F3E-B7B3-D2EAC499358E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {D8467B14-270C-4F3E-B7B3-D2EAC499358E}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {D8467B14-270C-4F3E-B7B3-D2EAC499358E}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {D8467B14-270C-4F3E-B7B3-D2EAC499358E}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {0E727625-26CA-4B4B-94F0-BAEE8D38B267} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /topics-list/topics.txt: -------------------------------------------------------------------------------- 1 | 3D 2 | Ajax 3 | Algorithm 4 | Amp 5 | Android 6 | Angular 7 | Ansible 8 | API 9 | Arduino 10 | ASP.NET 11 | Atom 12 | Awesome Lists 13 | Amazon Web Services 14 | Azure 15 | Babel 16 | Bash 17 | Bitcoin 18 | Bootstrap 19 | Bot 20 | C 21 | Chrome 22 | Chrome extension 23 | Command line interface 24 | Clojure 25 | Code quality 26 | Code review 27 | Compiler 28 | Continuous integration 29 | COVID-19 30 | C++ 31 | Cryptocurrency 32 | Crystal 33 | C# 34 | CSS 35 | Data structures 36 | Data visualization 37 | Database 38 | Deep learning 39 | Dependency management 40 | Deployment 41 | Django 42 | Docker 43 | Documentation 44 | .NET 45 | Electron 46 | Elixir 47 | Emacs 48 | Ember 49 | Emoji 50 | Emulator 51 | ESLint 52 | Ethereum 53 | Express 54 | Firebase 55 | Firefox 56 | Flask 57 | Font 58 | Framework 59 | Front end 60 | Game engine 61 | Git 62 | GitHub API 63 | Go 64 | Google 65 | Gradle 66 | GraphQL 67 | Gulp 68 | Hacktoberfest 69 | Haskell 70 | Homebrew 71 | Homebridge 72 | HTML 73 | HTTP 74 | Icon font 75 | iOS 76 | IPFS 77 | Java 78 | JavaScript 79 | Jekyll 80 | jQuery 81 | JSON 82 | The Julia Language 83 | Jupyter Notebook 84 | Koa 85 | Kotlin 86 | Kubernetes 87 | Laravel 88 | LaTeX 89 | Library 90 | Linux 91 | Localization (l10n) 92 | Lua 93 | Machine learning 94 | macOS 95 | Markdown 96 | Mastodon 97 | Material Design 98 | MATLAB 99 | Maven 100 | Minecraft 101 | Mobile 102 | Monero 103 | MongoDB 104 | Mongoose 105 | Monitoring 106 | MvvmCross 107 | MySQL 108 | NativeScript 109 | Nim 110 | Natural language processing 111 | Node.js 112 | NoSQL 113 | npm 114 | Objective-C 115 | OpenGL 116 | Operating system 117 | P2P 118 | Package manager 119 | Parsing 120 | Perl 121 | Phaser 122 | PHP 123 | PICO-8 124 | Pixel Art 125 | PostgreSQL 126 | Project management 127 | Publishing 128 | PWA 129 | Python 130 | Qt 131 | R 132 | Rails 133 | Raspberry Pi 134 | Ratchet 135 | React 136 | React Native 137 | ReactiveUI 138 | Redux 139 | REST API 140 | Ruby 141 | Rust 142 | Sass 143 | Scala 144 | scikit-learn 145 | Software-defined networking 146 | Security 147 | Server 148 | Serverless 149 | Shell 150 | Sketch 151 | SpaceVim 152 | Spring Boot 153 | SQL 154 | Storybook 155 | Support 156 | Swift 157 | Symfony 158 | Telegram 159 | Tensorflow 160 | Terminal 161 | Terraform 162 | Testing 163 | Twitter 164 | TypeScript 165 | Ubuntu 166 | Unity 167 | Unreal Engine 168 | Vagrant 169 | Vim 170 | Virtual reality 171 | Vue.js 172 | Wagtail 173 | Web Components 174 | Web app 175 | Webpack 176 | Windows 177 | WordPlate 178 | WordPress 179 | Xamarin 180 | XML --------------------------------------------------------------------------------