- Workflow: یکی از مفاهیمی که باید با آن آشنا باشیم workflowها هستند. یک workflow مجموعهایی از jobهایی هستند که در رخدادهای خاصی اجرا میشوند. در واقع یک workflow یک CI pipeline است که با کمک YAML آنها را تعریف میکنیم.
- Runner: اینها به اصطلاح compute machineهایی هستند که workflowها را اجرا میکنند. این runnerها هم میتوانند به صورت سفارشی باشند و هم سرویسهای ارائه شده توسط GitHub باشند.
- Job: مجموعهایی از مراحلی که درون یک runner workspace اجرا میشوند.
- Step: در نهایت stepها هستند که کوچکترین بخش GitHub Actions هستند. stepها میتوانند فایل اسکریپت، Dockerfile یا یک community action باشند.
name: Build Application Code on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 - name: Install Libraries uses: pip install -r requirements.txt -t . test: runs-on: ubuntu-latest needs: build steps: ...
├── .github │ ├── scripts │ └── workflows ├── README.md ├── assets └── deps
name: Update Recent Blog Posts on: schedule: - cron: "0 0 * * 0" # Run once a week at 00:00 (midnight) on Sunday workflow_dispatch: jobs: update_posts: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching latest blog posts shell: pwsh run: | . ./.github/scripts/Get-Posts.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Get-Posts { Param ( [Parameter(Mandatory = $false)] [string]$rssUrl ) $posts = @() $feed = [xml](Invoke-WebRequest -Uri $rssUrl).Content $feed.rss.channel.item | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.title."#cdata-section" ?? $_.title Link = $_.link Description = $_.description."#cdata-section" ?? $_.description PubDate = $_.pubDate } $posts += $post } $posts } Function Get-DntipsPosts { $assemblyPath = "$(Get-Location)/deps/CodeHollow.FeedReader.dll" [Reflection.Assembly]::LoadFile($assemblyPath) $feed = [CodeHollow.FeedReader.FeedReader]::ReadAsync("https://www.dntips.ir/feed/author/%d8%b3%db%8c%d8%b1%d9%88%d8%a7%d9%86%20%d8%b9%d9%81%db%8c%d9%81%db%8c").Result $posts = @() $feed.Items | Select-Object -First 3 | ForEach-Object { $post = [PSCustomObject]@{ Title = $_.Title Link = $_.Link Description = $_.Description PubDate = $_.PublishingDate } $posts += $post } $posts } Function Set-Posts { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSCustomObject[]]$posts, [Parameter(Mandatory = $false)] [string]$marker = "## Recent Blog Posts - English" ) Begin { $readMePath = "./README.md" $readmeContents = Get-Content -Path $readMePath -Raw $markdownTable = "| Link | Published At |`n" $markdownTable += "| --- | --- |`n" } Process { if ($null -eq $_.Title) { return } $date = Get-Date -Date $_.PubDate $link = "[$($_.Title)]($($_.Link))" $markdownTable += "| $($link) | $($date.ToString("dd/MM/yy")) |`n" } End { $updatedContent = $readmeContents -replace "$marker\n([\s\S]*?)(?=#| $)", "$marker`n$($markdownTable)`n" $updatedContent | Set-Content -Path $readMePath } } Function Set-Blogs { $recentBlogPostsStr = "## Recent blog posts -" Get-Posts("https://dev.to/feed/sirwanafifi") | Set-Posts -marker "$recentBlogPostsStr dev.to" Get-Posts("https://sirwan.infohttps://www.dntips.ir/rss.xml") | Set-Posts -marker "$recentBlogPostsStr sirwan.info" Get-DntipsPosts | Set-Posts -marker "$recentBlogPostsStr dntips.ir" } Set-Blogs
name: Update Step Component on: schedule: - cron: "0 18 * * *" workflow_dispatch: jobs: update_steps: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v3 - name: Run the script for fetching my latest steps shell: pwsh env: STEPS_URI: ${{ secrets.STEPS_URI }} run: | . ./.github/scripts/Get-Steps.ps1 - name: Commit and Push the changes uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Function Set-Steps { Param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject]$json ) Write-Host ($json | ConvertTo-Json) $SvgPath = "$(Get-Location)/assets/step.svg" $SvgContent = Get-Content -Path $SvgPath -Raw $TextTags = @" <tspan id="step-count" font-weight="bold">$([System.String]::Format("{0:n0}", [int]$json.steps))</tspan> "@ $DatetimeTags = "<text id=""datetime"" x=""800"" y=""72"" font-size=""39"" fill="#99989E"">$($json.date)</text>" $SvgContent = $SvgContent -Replace '<tspan id="step-count" font-weight="bold">.*?</tspan>', $TextTags $SvgContent = $SvgContent -Replace '<text id="datetime" x="800" y="72" font-size="39" fill="#99989E">.*?</text>', $DatetimeTags $SvgContent | Set-Content -Path $SvgPath } Function Get-LatestSteps { Try { $Uri = $env:STEPS_URI Write-Host "Uri: $Uri" $JsonResult = (Invoke-WebRequest -Uri $Uri).Content | ConvertFrom-Json Write-Host "Steps: $($JsonResult.steps)" Return $JsonResult } Catch { Return @{ steps = 0 date = Get-Date -Format "yyyy-MM-dd" } } } Write-Host "Getting latest steps..." Get-LatestSteps | Set-Steps Write-Host "Done!"
NET MAUI Preview 5. منتشر شد
The Kubernetes project plans to deprecate Docker Engine support in the kubelet and support for dockershim will be removed in a future release, probably late next year.
پکیج منجر gem
Features
حرکت دادن المانها در صفحه
ابتدا قطعه کد HTML زیر را درنظر بگیرید:
<body> <h2>Flavors</h2> <ul class="flavors"> <li>chocolate</li> <li>strawberry</li> <li>vanilla</li> </ul> <h2>Types</h2> <ul class="types"> <li>frozen yogurt</li> <li>custard</li> <li>Italian ice</li> </ul> <ul class="unassigned"> <li>rocky road</li> <li>gelato</li> </ul> </body>
<body> <h2>Types</h2> <ul class="types"> <li>frozen yogurt</li> <li>Italian ice</li> <li>custard</li> <li>gelato</li> </ul> <h2>Flavors</h2> <ul class="flavors"> <li>chocolate</li> <li>vanilla</li> <li>rocky road</li> <li>strawberry</li> </ul> <ul class="unassigned"> </ul> </body>
حرکت دادن المانها توسط jQuery
var $flavors = $('.flavors'); var $chocolate = $flavors.find('li').eq(0); var $vanilla = $flavors.find('li').eq(2); $chocolate.after($vanilla);
در ادامه میخواهیم لیست types را به همراه عنوان آن، به قبل از لیست flavors منتقل کنیم:
var $typesHeading = $('h2').eq(1); $typesHeading.prependTo('body'); $typesHeading.after($('.types'));
سپس در لیست unassigned ابتدا rocky road آنرا یافته و به بالای strawberry در لیست flavors اضافه میکنیم. همچنین gelato آنرا نیز یافته و به انتهای لیست types اضافه خواهیم کرد:
var $unassigned = $('.unassigned'); var $rockyRoad = $unassigned.find('li').eq(0); var $gelato = $unassigned.find('li').eq(1); $vanilla.after($rockyRoad); $gelato.appendTo($('.types'));
حرکت دادن المانها توسط جاوا اسکریپت خالص (سازگار با IE 8.0 به بعد)
در ابتدا میخواهیم المان vanilla را به قبل از المان strawberry حرکت دهیم. برای اینکار میتوان از متد استاندارد insertBefore استفاده کرد:
var flavors = document.querySelector('.flavors'); var strawberry = flavors.children[1]; var vanilla = flavors.children[2]; flavors.insertBefore(vanilla, strawberry);
سپس کار انتقال عنوان لیست types و خود آن به قبل از لیست flavors صورت میگیرد:
var headings = document.querySelectorAll('h2'); var flavorsHeading = headings[0]; var typesHeading = headings[1]; var typesList = document.querySelector('.types'); document.body.insertBefore(typesHeading, flavorsHeading); document.body.insertBefore(typesList, flavorsHeading);
در آخر میخواهیم آیتمهای لیست unassigned را به لیستهای مرتبط با آنها انتقال دهیم:
flavors.insertBefore(document.querySelector('.unassigned > li'), strawberry); document.querySelector('.types').appendChild(document.querySelector('.unassigned > li'));
در سطر دوم، چون هم اکنون المان rocky road از لیست unassigned حذف شدهاست، متد querySelector فراخوانی شده، اولین عنصر لیست یا همان gelato را بازگشت میدهد. این المان را توسط متد appendChild به انتهای لیست types اضافه خواهیم کرد. متد appendChild نیز همانند متد insertBefore نیاز به یک والد دارد که همان عنصری است که قرار است المانها به آن افزوده شوند.
کپی کردن المانها
<ol class="numbers"> <li>one</li> <li>two</li> </ol>
// deep clone: return value is an exact copy $('.numbers').clone();
و در جاوا اسکریپت خالص (سازگار با IE 8.0 به بعد) برای کپی کردن المانها دو روش shallow و deep وجود دارد:
// shallow clone: return value is an empty <ol> document.querySelector('.numbers').cloneNode(); // deep clone: return value is an exact copy of the tree document.querySelector('.numbers').cloneNode(true);
باید دقت داشت که متد cloneNode آنچه را که مشاهده میکنید یا همان اصل markup را کپی میکند. بنابراین اگر از طریق جاوا اسکریپت تغییراتی را در آن شیء داده باشید در متد cloneNode لحاظ نمیشود.
بدیهی است المانهای clone شده تا زمانیکه با متدهایی مانند insertBefore و یا appendChild به صفحه اضافه نشوند، در صفحه نمایان نخواهند شد.
ایجاد و حذف المانها
فرض کنید میخواهیم به لیست flavors مثال ابتدای بحث، دو مورد جدید را اضافه کنیم.
روش افزودن المانهای جدید توسط جیکوئری:
var $flavors = $('.flavors'); // add two new flavors $('<li>pistachio</li>').appendTo($flavors); $('<li>neapolitan</li>').appendTo($flavors);
// remove the "gelato" type $('.types li:last').remove();
روش افزودن المانهای جدید توسط جاوا اسکریپت خالص:
var flavors = document.querySelector('.flavors'); // add two new flavors flavors.insertAdjacentHTML('beforeend', '<li>pistachio</li>') flavors.insertAdjacentHTML('beforeend', '<li>neapolitan</li>')
// remove the "gelato" type document.querySelector('.types li:last-child').remove();
روش دیگر انجام اینکار به صورت زیر توسط متد removeChild است:
var gelato = document.querySelector('.types li:last-child'); // remove the "gelato" type gelato.parentNode.removeChild(gelato);
کار با المانهای متنی
در جیکوئری متد ()text آن امکان دریافت محتوای متنی و همچنین به روز رسانی آنرا میسر میکند:
$('.types li').eq(1).text('italian ice');
در جاوا اسکریپت خالص، دو خاصیت textContent و همچنین innerText برای خواندن و یا به روز رسانی محتوای متنی عناصر مورد استفاده قرار میگیرند. برای مثال معادل قطعه کد جیکوئری فوق که از متد text استفاده میکند با جاوا اسکریپت خالص به صورت زیر است:
document.querySelectorAll('.types li')[1].textContent = 'italian ice';
خاصیت innerText هرچند بر روی اینترفیس HTMLElement تعریف شدهاست، اما جزء هیچکدام از استانداردهای وب نیست؛ ولی توسط تمام مرورگرهای امروزی پشتیبانی میشود. در این حالت به روز رسانی متن توسط آن با خاصیت textContent دقیقا یکی است؛ اما خروجی آن برعکس حالتهای قبل، متن رندر شدهی المانها را بازگشت میدهد. برای مثال در اینجا شامل فاصلههای پیش از این المانها در markup نمیشود.
برای مثال این قسمتی از خروجی خاصیت textContent است:
Flavors chocolate vanilla rocky road strawberry
Flavors chocolate vanilla rocky road strawberry
گاهی از اوقات از سرور قطعهای کد HTML ایی را دریافت میکنیم (که هنوز به صورت المان یا المانهای DOM در نیامدهاست) و در سمت کلاینت میخواهیم آنرا به قسمتی از صفحه اضافه کنیم. روش انجام اینکار در jQuery به صورت زیر است:
var container = '<h2>Containers</h2><ul><li>cone</li><li>cup</li></ul>'; $('<div>').html(container).appendTo('body');
روش دریافت محتوای رشتهای HTML قابل ارسال به سرور نیز به صورت زیر است:
var contents = $('body').html();
var div = document.createElement('div'); div.innerHTML = container; document.body.appendChild(div);
روش خواندن این محتوای نهایی نیز به صورت زیر است:
var contents = document.body.innerHTML;
برای شروع کار با Backload ابتدا به قسمت Nutget میرویم که در مسیر زیر است :
Backload. A Proffesional Full Featured Asp.Net FileUpload Controller *
پس از نصب دو مورد بالا ، موارد زیر را که در دو عکس پایین میبینید، به پروژهی شما اضافه خواهند شد:
*نکاتی که باید بدانید:
عکس هایی که شما آپلود میکنید در پوشهی Files موجود در ریشهی پروژهی شما قرار میگیرند این پوشه بوسیلهی خود ابزار Backload ساخته میشود.
چنانچه بخواهید در پوشهی Files پوشهای ایجاد کنید، ابتدا View آنرا باز کنید. این View در پروژه، در مسیر Views/ BackloadDemo/Index قرار دارد .
در داخل تگ form یک Hidden Field با نام objectContext اضافه میکنید. Value ایی که شما به این Hidden Field نسبت میدهید، نام پوشهی شما در پوشهی Files خواهد بود. مانند تصویر زیر: در اینجا پوشهای با نام 2014-02 در پوشهی Files وقتی که فایلی را باFile Upload آپلود میکنیم، ایجاد میشود.
چنانچه بخواهید در پوشهای که خودتان ایجاد کردید (که در این مثال 2014-02 میباشد) پوشههای متعدد دیگری هم داشته باشید Hidden Field ای با نام uploadContext ایجاد میکنید؛ مانند تصویر زیر :
اکنون اگر فایل جدیدی را آپلود کنید در مسیر
ذخیره میشود . یعنی بین نام هر پوشه از سمی کولن ; در Value استفاده میکنید.
تا اینجا ما میتوانیم بوسیلهی ابزار Backload عکسها را آپلود ، حذف و مسیر آپلود عکسها را تغییر دهیم.اگر توجه کرده باشید دفعات بعد که پروژه را اجرا میکنید عکسهای موجود در پوشه، در گرید ویو File Upload به شما نمایش داده خواهد شد. حال اگر بخوایم عکسهای موجود در پوشهی دیگری را نمایش دهیم باید چه کار کنیم؟!
گاهی اوقات نیاز داریم که محتویات پوشهای خاص را در گرید ویو File Upload مان نمایش دهیم. برای این کار شما باید کنترلر FileUploadController که در فایل ضمیمه در آموزش هست را در پوشهی Controller پروژهی خود کپی کنید .اگر شما این کنترلر را باز کنید مواردی مانند کلمه کلیدی Task و async را مشاهده خواهید کرد. این موارد در .Net Framework 4 شناسایی نمیشود. برای همین در ابتدای آموزش تاکید کردم که .Net Framework 4.5 و یا بالاتر را برای پروژهی خود انتخاب کنید .
در تابع Handler_GetFilesRequestStarted در این کنترلر باید مشخص کنید که فایلهای موجود در کدام مسیر را برای شما نمایش دهد؛ آن هم با استفاده از دستور زیر :
اکنون قبل از آنکه پروژه را اجرا کنید فایل Backload.Demo.js که در مسیر Scripts/Fileupload هست را باز کنید و url موجود در آنرا مانند عکس زیر تغیییر دهید :
حالا پروژه را اجرا کنید. خواهید دید تمامی فایلهای موجود در مسیری را که شما مشخص کردهاید، برایتان نمایش خواهد داد.
چنانچه بخواهید تعداد مثلا 5 فایل برای شما در گرید ویو مربوط به FileUpload نمایش داده شود، به تابع handler_GetFilesRequestFinished میروید. متغیر limit مشخص میکند که 5 فایل نمایش داده شود. میتوانید این عدد را به دلخواه خود تغییر دهید.
نکتهی بسیار مهم دیگری که باید به آن توجه شود مربوط به Hidden Field نام پوشهها میباشد. بار دیگر پروژه را اجرا کنید. با استفاده از ابزاری مثل FireBug کدهای html صفحهی خود را ببینید. Hidden Field ایی با نام objectContext را پیدا کنید و Value آنرا به test تغییر دهید. فایلی را آپلود کنید حالا به پوشهی Files موجود در ریشهی پروژه بروید. میبینید که پوشهای با نام testایجاد شده و فایلی هم که آپلود کردید در آن قرار دارد که این یک اشکال است. برای اینکه جلوی این گونه کارها را بگیریم به تابع handler_StoreFileRequestStartedAsync میرویم و کد زیر را مینویسیم :
دستور e.Context.PipelineControl.ExecutePipeline = false; باعث میشود که اجرای تابع متوقف شود.
فایل ضمیمه :FileUploadController-462d551688cf48c68cb55343ac5464f3.zip
برای مشاهده مثالهای دیگری دربارهی Backload به این لینک بروید.
موفق باشید.
این نوشتار اولین آموزش من در این سایت میباشد و جا دارد از دوست خوبم "محبوبه قربانی" که باعث شد من با MVC آشنا شوم تشکر کنم.