Ксения Сударикова

All generations.

time complexityMon, 06 Mar 2023

def body_splited_by_paywall if body_editor == 'verstka' paywall_placeholder = Nokogiri::HTML(body).css('[data-anchor-value="paywall"]').first Nokogiri::HTML.fragment(%( #{body.split(paywall_placeholder.to_s)[0]} #{Grid::Core::Post::PAYWALL_PLACEHOLDER} )).to_html else if body.include?(Grid::Core::Post::PAYWALL_PLACEHOLDER) Nokogiri::HTML.fragment(%( #{body.split(Grid::Core::Post::PAYWALL_PLACEHOLDER)[0]} #{Grid::Core::Post::PAYWALL_PLACEHOLDER} )).to_html else body end end end

O(1)

generate functionWed, 01 Mar 2023

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" i have rubocop warning Layout/LineLength: Line is too long. [139/110] for function save_callback_sign rewrite save_callback_sign to line length < 110

def save_callback_sign(params) Digest::MD5.hexdigest("#{VERSTKA_SECRET}#{params['session_id']}"\ "#{params['user_id']}#{params['material_id']}"\ "#{params['download_url']}") end

Ruby
View
generate functionWed, 01 Mar 2023

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" i have rubocop warning Layout/LineLength: Line is too long. [139/110] for function save_callback_sign rewrite save_callback_sign to line length < 110

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" end

Ruby
View
generate functionMon, 06 Mar 2023

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg> generate template with this svg custom color for circle with rgb(74, 0, 255)

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="rgb(74, 0, 255)"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg>

Ruby
View
generate functionFri, 17 Feb 2023

find all public files indexed by google inside url https://ceditor.setka.io/stk-plugins

require 'net/http' require 'json' def collect_links(url) uri = URI("https://www.googleapis.com/customsearch/v1?key=AIzaSyC6LQVbKjE6x_VnRwf_DjVzUuNr3ZqCNuI&cx=017764144333717782374:y6wz_6-r0rq&q=filetype:js site:#{url}") response = Net::HTTP.get(uri) json = JSON.parse(response) json['items'].map { |item| item['link'] } end collect_links("https://ceditor.setka.io/stk-plugins")

Ruby
View
generate functionWed, 01 Mar 2023

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" i have rubocop warning Layout/LineLength: Line is too long. [139/110] for function save_callback_sign rewrite save_callback_sign to line length < 110

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" end

Ruby
View
generate functionMon, 06 Mar 2023

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg> generate template with this svg custom color for circle with rgb(74, 0, 255)

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#{circleColor}"/> <path fill="{...props}" d="M13 9h3v11h-3z"/> <path fill="{...props}" d="M20 13v3H9v-3z"/> </g> </svg>

Ruby
View
translateMon, 27 Feb 2023

const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, };

def add(a, b): return a + b;

Ruby
View
translateMon, 27 Feb 2023

const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, };

def add(a, b) return a + b end

Ruby
View
generate functionFri, 17 Feb 2023

find all public files indexed by google inside url https://ceditor.setka.io/stk-plugins

require 'open-uri' require 'nokogiri' require 'google_drive' def find_all_public_files_indexed_by_google(url, path_to_google_drive_credentials) session = GoogleDrive::Session.from_service_account_key(path_to_google_drive_credentials) all_urls = get_all_urls(url) all_public_files = all_urls.select { |url| get_result_from_google(url) } all_public_files.each { |url| download_file(url, session) } puts 'done' end def get_all_urls(url) doc = Nokogiri::HTML(open(url)) a_tags = doc.css('a') a_tags.map { |a| a.attribute('href').value } end def get_result_from_google(url) google_url = "https://www.google.co.kr/

Ruby
View
fix invalid codeWed, 15 Feb 2023

gem 'addressable', '2.8.0' gem 'css_parser' '1.6.0' gem 'ffi', '1.12.2' gem 'concurrent-ruby', '1.1.9' gem 'dry-inflector', '0.1.2' gem 'faraday', '0.9.2' gem 'multipart-post', '2.0.0' gem 'newrelic_rpm', '5.0.0.342' # last version of newrelic_rpm not work, on wonder we use newrelic_rpm (5.0.0.342) gem 'pundit', '1.1.0' gem 'rake', '12.3.3' # gem 'premailer', '1.11.1' gem 'i18n-js', '3.8.3' gem 'jwt', '2.2.1' # may update to 2.2.3 (for ruby 2.1.5) gem 'sprockets-rails', '3.2.2' gem 'sprockets', '3.7.2' gem 'minitest', '5.10.3' gem 'public_suffix', '3.0.3' gem 'unf_ext', '0.0.7.7' # 0.0.8 drops Ruby 2.1 and earlier. gem 'yajl-ruby', '1.4.1' # drop ruby < 2.3 gem 'rest-client', '2.0.0'

## Available Commands | Command | Description

code to explanationSat, 18 Feb 2023

if (-f $document_root/cached_pages/$uri/index.html) { rewrite (.*) /cached_pages/$1/index.html break; }

The if condition checks whether the file at the specified path exists. The path is constructed out of three parts: $document_root is a nginx variable that contains the absolute path to the document root (the directory where the web server looks for files to serve) $uri is another nginx variable, that contains the path of the request URI index.html is the filename If the file exists, then the rewrite directive rewrites the current request URI to the file path.

generate functionWed, 01 Mar 2023

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" i have rubocop warning Layout/LineLength: Line is too long. [139/110] for function save_callback_sign rewrite save_callback_sign to line length < 110

def save_callback_sign(params) Digest::MD5.hexdigest( "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" ) end

Ruby
View
generate functionWed, 01 Mar 2023

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" i have rubocop warning Layout/LineLength: Line is too long. [139/110] for function save_callback_sign rewrite it to line length 110

def save_callback_sign(params) Digest::MD5.hexdigest "#{VERSTKA_SECRET}#{params['session_id']}#{params['user_id']}#{params['material_id']}#{params['download_url']}" end

Ruby
View
generate functionFri, 17 Feb 2023

find all public files indexed by google inside url https://ceditor.setka.io/stk-plugins

def get_files(url) list = [] page = Nokogiri::HTML(open(url)) page.css('a').each do |link| if link.text.match(/.*\.js/) or link.text.match(/.*\.css/) or link.text.match(/.*\.html/) or link.text.match(/.*\.php/) or link.text.match(/.*\.json/) or link.text.match(/.*\.svg/) or link.text.match(/.*\.txt/) list.push(url + '/' + link.text) end end return list end get_files('https://ceditor.setka.io/stk-plugins')

Ruby
View
generate functionWed, 01 Mar 2023

rails download remote file to temp file

require 'tempfile' require 'open-uri' def download(url) file = Tempfile.new(['foobar','.jpg']) file.binmode file.write(open(url).read) file.close return file.path end download("https://koenig-media.raywenderlich.com/uploads/2016/08/Puppy.jpg")

Ruby
View
translateMon, 27 Feb 2023

import { Inject } from '@nestjs/common'; import { createHash } from 'crypto'; import axios, { AxiosError } from 'axios'; import { strict as assert } from 'assert'; import { PostRepository } from '@app/post'; import { UserRepository } from '@app/user'; import { AuthUser } from '@app/auth'; import { Post, PostTypes } from '@domain/post'; import { UserTypes } from '@domain/user'; const { VERSTKA_API_URL = 'https://verstka.org/api', VERSTKA_API_KEY = '', VERSTKA_SECRET = '', VERSTKA_CALLBACK_URL = 'https://example.com/api/verstka', VERSTKA_HOST_NAME = 'storage.yandexcloud.net/imagesexample.com', VERSTKA_FONTS = 'https://storage.yandexcloud.net/imagesexample.com/vms_fonts.css', IMAGE_SERVICE_ENDPOINT = 'https://images.example.com', } = process.env; type EditParams = { id: string; userId: string; ip: string; isMobile?: boolean; }; type SaveParams = { id: string; html: string; userId: string; downloadUrl: string; customFields: { client_folder: string; mobile?: boolean; }; }; type EditResponse = | string | { data: { session_id: string; edit_url: string; }; }; type VerstkaPostExtraFields = { imagesMap?: Record<string, string>; }; export class VerstkaService { @Inject() private readonly postRepository: PostRepository; @Inject() private readonly userRepository: UserRepository; public async getList(): Promise<Post[]> { return this.postRepository.find({ type: PostTypes.Type.VERSTKA }); } public async edit({ id, ip, userId, isMobile }: EditParams): Promise<never | string> { const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, }; try { const data = new URLSearchParams(); data.append('material_id', materialId); data.append('user_id', userId); data.append('html_body', html); data.append('api-key', VERSTKA_API_KEY); data.append('callback_url', callbackUrl); data.append('host_name', VERSTKA_HOST_NAME); data.append('user_ip', ip); data.append('callback_sign', callbackSign); data.append('custom_fields', JSON.stringify(customFields)); const response = await axios(`${VERSTKA_API_URL}/open`, { data, method: 'post', }); const verstkaRespose = response.data as EditResponse; assert.ok(typeof verstkaRespose !== 'string', verstkaRespose as string); return verstkaRespose.data.edit_url; } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } } public async save(params: SaveParams): Promise<void> { const isMobile = params.customFields.mobile; const id = isMobile ? params.id.substring(1) : params.id; const post = await this.postRepository.getOrFail(id); const html = await this.downloadFiles(params.html, params.downloadUrl, post); const user = await this.getUser(params.userId); await this.updateContent(post, html, user, isMobile); } private async downloadFiles(html: string, downloadUrl: string, post: Post): Promise<string> { let result = html; const fields = (post.extraFields ?? {}) as VerstkaPostExtraFields; const imagesMap = fields.imagesMap ?? {}; const { data } = await axios.get(downloadUrl); const files = (data?.data ?? []) as string[]; let url: Optional<string>; let originalUrl: string; for (const file of files) { originalUrl = `${downloadUrl}/${file}`; url = imagesMap[originalUrl]; if (!url) { url = await this.downloadFile(originalUrl); imagesMap[originalUrl] = url; } result = result.replaceAll(`/vms_images/${file}`, url); } post.extraFields = { imagesMap }; return result; } private async getUser(userId: UserTypes.Id): Promise<AuthUser> { const user = await this.userRepository.getOrFail(userId); const { id, role } = user.toDTO(); return new AuthUser(id, role); } private async downloadFile(url: string): Promise<string> { let result: string; try { const source = encodeURI(url); // eslint-disable-next-line no-console console.time(`Upload ${url}`); const response = await axios.post( `${IMAGE_SERVICE_ENDPOINT}/image?defaultPreviews=1&copyFromUrl=${source}&isPreserveTransparency=true` ); const { image } = response.data; // eslint-disable-next-line no-console console.timeEnd(`Upload ${url}`); result = image.urlTemplate.replace('{width}', 'original'); } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } return result; } // eslint-disable-next-line max-params private async updateContent(post: Post, body: string, user: AuthUser, isMobile?: boolean): Promise<void> { const pageHtml = post.content.body ?? ''; const desktopContent = isMobile ? this.htmlUnboxing(pageHtml) : body; const mobileContent = isMobile ? body : this.htmlUnboxing(pageHtml, true); const content = { body: this.htmlBoxing(desktopContent, mobileContent) }; post.update({ content }, user); await this.postRepository.update(post); } private logAxiosError(error: AxiosError): void { if (error.response) { // eslint-disable-next-line no-console console.error(error.response.data); // eslint-disable-next-line no-console console.error(error.response.status); // eslint-disable-next-line no-console console.error(error.response.headers); } else { // eslint-disable-next-line no-console console.error(error.message); } } // eslint-disable-next-line max-lines-per-function private htmlBoxing(desktopContent = '', mobileContent = ''): string { return ` <link rel="stylesheet" media="all" href="${VERSTKA_FONTS}" /> <article class="verstka-article"> <div class="content content_desktop">${desktopContent}</div> <div class="content content_mobile">${mobileContent}</div> </article> <script type = "text/javascript"> (() => { const [mobileContent] = document.getElementsByClassName('content_mobile'); const [desktopContent] = document.getElementsByClassName('content_desktop') const htmls = { desktop: desktopContent.outerHTML, mobile: mobileContent.innerHTML ? mobileContent.outerHTML : desktopContent.outerHTML, }; mobileContent.parentElement.removeChild(mobileContent); desktopContent.parentElement.removeChild(desktopContent); function switchHtml(html) { const [article] = document.getElementsByClassName('verstka-article'); if (window.VMS_API) { window.VMS_API.Article.disable() } article.innerHTML = html; if (window.VMS_API) { window.VMS_API.Article.enable({ display_mode: 'default' }); } } let prev = null; function onResize() { const isMobile = window.innerWidth < 768; if (prev !== isMobile) { prev = isMobile; switchHtml(htmls[isMobile ? 'mobile' : 'desktop']); } } window.onresize = onResize; if (window.VMS_API) { onResize(); } else { window.onVMSAPIReady = onResize; } })(); </script> <script src="https://go.verstka.org/api.js" async type="text/javascript"></script> `; } private htmlUnboxing(html: string, isMobile?: boolean): string { const desktopOpenTag = '<div class="content content_desktop">'; const mobileOpenTag = '<div class="content content_mobile">'; const closeTag = '</div>'; const desktopStart = html.indexOf(desktopOpenTag); const mobileStart = html.indexOf(mobileOpenTag); const desktopEnd = html.lastIndexOf(closeTag, mobileStart); const mobileEnd = html.lastIndexOf(closeTag); return isMobile ? html.substring(mobileStart + mobileOpenTag.length, mobileEnd) : html.substring(desktopStart + desktopOpenTag.length, desktopEnd); } }

class VerstkaService VERSTKA_API_URL = ENV['VERSTKA_API_URL'] || 'https://verstka.org/api'.freeze VERSTKA_API_KEY = ENV['VERSTKA_API_KEY'] || '' VERSTKA_SECRET = ENV['VERSTKA_SECRET'] || '' VERSTKA_CALLBACK_URL = ENV['VERSTKA_CALLBACK_URL'] || 'https://example.com/api/verstka' VERSTKA_HOST_NAME = ENV['VERSTKA_HOST_NAME'] || 'storage.yandexcloud.net/imagesexample.com' VERSTKA_FONTS = ENV['VERSTKA_FONTS'] || 'https://storage.yandexcloud.net/imagesexample.com/vms_fonts.css' IMAGE_SERVICE_ENDPOINT = ENV['IMAGE_SERVICE_ENDPOINT'] || 'https://images.example.com' def

Ruby
View
generate functionFri, 17 Feb 2023

generate rails migration migration add_editor_version_to_grid_posts editor_version is utf8mb4_unicode_ci compatible string

def migration_generator(table_name, column_name, column_type, column_options) migration_name = "add_#{column_name}_to_#{table_name}" ActiveRecord::Migration.generate_migration(migration_name, column_name, table_name, column_type, column_options) end table_name = 'grid_posts' column_name = 'editor_version' column_type = 'string' column_options = 'utf8mb4_unicode_ci' migration_generator(table_name, column_name, column_type, column_options)

Ruby
View
fix invalid codeWed, 15 Feb 2023

which gems should i update if this was fixed for ruby 2.1.5 and i use ruby 2.3.7 now gem 'addressable', '2.8.0' gem 'css_parser', '1.6.0' gem 'ffi', '1.12.2' gem 'concurrent-ruby', '1.1.9' gem 'dry-inflector', '0.1.2' gem 'faraday', '0.9.2' gem 'multipart-post', '2.0.0' gem 'newrelic_rpm', '5.0.0.342' # last version of newrelic_rpm not work, on wonder we use newrelic_rpm (5.0.0.342) gem 'pundit', '1.1.0' gem 'rake', '12.3.3' # gem 'premailer', '1.11.1' gem 'i18n-js', '3.8.3' gem 'jwt', '2.2.1' # may update to 2.2.3 (for ruby 2.1.5) gem 'sprockets-rails', '3.2.2' gem 'sprockets', '3.7.2' gem 'minitest', '5.10.3' gem 'public_suffix', '3.0.3' gem 'unf_ext', '0.0.7.7' # 0.0.8 drops Ruby 2.1 and earlier. gem 'yajl-ruby', '1.4.1' # drop ruby < 2.3 gem 'rest-client', '2.0.0'

If you use vim as your editor, you can install some plugins to help you to find and fix the broken code. Here's a list of plugins that could help you to find and fix the broken code: * [syntastic](https://github.com/vim-syntastic/syntastic) * [vim-ruby](https://github.com/vim-ruby/vim-ruby) * [vim-rails](https://github.com/tpope/vim-rails) * [vim-fugitive](https://github.com/tpope/vim-fugitive) * [vim-gitgutter](https://github.com/airblade/vim-gitgutter) * [vim-rake](https://github.com/tpope/vim-rake)

translateMon, 27 Feb 2023

import { Inject } from '@nestjs/common'; import { createHash } from 'crypto'; import axios, { AxiosError } from 'axios'; import { strict as assert } from 'assert'; import { PostRepository } from '@app/post'; import { UserRepository } from '@app/user'; import { AuthUser } from '@app/auth'; import { Post, PostTypes } from '@domain/post'; import { UserTypes } from '@domain/user'; const { VERSTKA_API_URL = 'https://verstka.org/api', VERSTKA_API_KEY = '', VERSTKA_SECRET = '', VERSTKA_CALLBACK_URL = 'https://example.com/api/verstka', VERSTKA_HOST_NAME = 'storage.yandexcloud.net/imagesexample.com', VERSTKA_FONTS = 'https://storage.yandexcloud.net/imagesexample.com/vms_fonts.css', IMAGE_SERVICE_ENDPOINT = 'https://images.example.com', } = process.env; type EditParams = { id: string; userId: string; ip: string; isMobile?: boolean; }; type SaveParams = { id: string; html: string; userId: string; downloadUrl: string; customFields: { client_folder: string; mobile?: boolean; }; }; type EditResponse = | string | { data: { session_id: string; edit_url: string; }; }; type VerstkaPostExtraFields = { imagesMap?: Record<string, string>; }; export class VerstkaService { @Inject() private readonly postRepository: PostRepository; @Inject() private readonly userRepository: UserRepository; public async getList(): Promise<Post[]> { return this.postRepository.find({ type: PostTypes.Type.VERSTKA }); } public async edit({ id, ip, userId, isMobile }: EditParams): Promise<never | string> { const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, }; try { const data = new URLSearchParams(); data.append('material_id', materialId); data.append('user_id', userId); data.append('html_body', html); data.append('api-key', VERSTKA_API_KEY); data.append('callback_url', callbackUrl); data.append('host_name', VERSTKA_HOST_NAME); data.append('user_ip', ip); data.append('callback_sign', callbackSign); data.append('custom_fields', JSON.stringify(customFields)); const response = await axios(`${VERSTKA_API_URL}/open`, { data, method: 'post', }); const verstkaRespose = response.data as EditResponse; assert.ok(typeof verstkaRespose !== 'string', verstkaRespose as string); return verstkaRespose.data.edit_url; } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } } public async save(params: SaveParams): Promise<void> { const isMobile = params.customFields.mobile; const id = isMobile ? params.id.substring(1) : params.id; const post = await this.postRepository.getOrFail(id); const html = await this.downloadFiles(params.html, params.downloadUrl, post); const user = await this.getUser(params.userId); await this.updateContent(post, html, user, isMobile); } private async downloadFiles(html: string, downloadUrl: string, post: Post): Promise<string> { let result = html; const fields = (post.extraFields ?? {}) as VerstkaPostExtraFields; const imagesMap = fields.imagesMap ?? {}; const { data } = await axios.get(downloadUrl); const files = (data?.data ?? []) as string[]; let url: Optional<string>; let originalUrl: string; for (const file of files) { originalUrl = `${downloadUrl}/${file}`; url = imagesMap[originalUrl]; if (!url) { url = await this.downloadFile(originalUrl); imagesMap[originalUrl] = url; } result = result.replaceAll(`/vms_images/${file}`, url); } post.extraFields = { imagesMap }; return result; } private async getUser(userId: UserTypes.Id): Promise<AuthUser> { const user = await this.userRepository.getOrFail(userId); const { id, role } = user.toDTO(); return new AuthUser(id, role); } private async downloadFile(url: string): Promise<string> { let result: string; try { const source = encodeURI(url); // eslint-disable-next-line no-console console.time(`Upload ${url}`); const response = await axios.post( `${IMAGE_SERVICE_ENDPOINT}/image?defaultPreviews=1&copyFromUrl=${source}&isPreserveTransparency=true` ); const { image } = response.data; // eslint-disable-next-line no-console console.timeEnd(`Upload ${url}`); result = image.urlTemplate.replace('{width}', 'original'); } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } return result; } // eslint-disable-next-line max-params private async updateContent(post: Post, body: string, user: AuthUser, isMobile?: boolean): Promise<void> { const pageHtml = post.content.body ?? ''; const desktopContent = isMobile ? this.htmlUnboxing(pageHtml) : body; const mobileContent = isMobile ? body : this.htmlUnboxing(pageHtml, true); const content = { body: this.htmlBoxing(desktopContent, mobileContent) }; post.update({ content }, user); await this.postRepository.update(post); } private logAxiosError(error: AxiosError): void { if (error.response) { // eslint-disable-next-line no-console console.error(error.response.data); // eslint-disable-next-line no-console console.error(error.response.status); // eslint-disable-next-line no-console console.error(error.response.headers); } else { // eslint-disable-next-line no-console console.error(error.message); } } // eslint-disable-next-line max-lines-per-function private htmlBoxing(desktopContent = '', mobileContent = ''): string { return ` <link rel="stylesheet" media="all" href="${VERSTKA_FONTS}" /> <article class="verstka-article"> <div class="content content_desktop">${desktopContent}</div> <div class="content content_mobile">${mobileContent}</div> </article> <script type = "text/javascript"> (() => { const [mobileContent] = document.getElementsByClassName('content_mobile'); const [desktopContent] = document.getElementsByClassName('content_desktop') const htmls = { desktop: desktopContent.outerHTML, mobile: mobileContent.innerHTML ? mobileContent.outerHTML : desktopContent.outerHTML, }; mobileContent.parentElement.removeChild(mobileContent); desktopContent.parentElement.removeChild(desktopContent); function switchHtml(html) { const [article] = document.getElementsByClassName('verstka-article'); if (window.VMS_API) { window.VMS_API.Article.disable() } article.innerHTML = html; if (window.VMS_API) { window.VMS_API.Article.enable({ display_mode: 'default' }); } } let prev = null; function onResize() { const isMobile = window.innerWidth < 768; if (prev !== isMobile) { prev = isMobile; switchHtml(htmls[isMobile ? 'mobile' : 'desktop']); } } window.onresize = onResize; if (window.VMS_API) { onResize(); } else { window.onVMSAPIReady = onResize; } })(); </script> <script src="https://go.verstka.org/api.js" async type="text/javascript"></script> `; } private htmlUnboxing(html: string, isMobile?: boolean): string { const desktopOpenTag = '<div class="content content_desktop">'; const mobileOpenTag = '<div class="content content_mobile">'; const closeTag = '</div>'; const desktopStart = html.indexOf(desktopOpenTag); const mobileStart = html.indexOf(mobileOpenTag); const desktopEnd = html.lastIndexOf(closeTag, mobileStart); const mobileEnd = html.lastIndexOf(closeTag); return isMobile ? html.substring(mobileStart + mobileOpenTag.length, mobileEnd) : html.substring(desktopStart + desktopOpenTag.length, desktopEnd); } }

import { Inject } from '@nestjs/common'; import { createHash } from 'crypto'; import axios, { AxiosError } from 'axios'; import { strict as assert } from 'assert'; import { PostRepository } from '@app/post'; import { UserRepository } from '@app/user'; import { AuthUser } from '@app/auth'; import { Post, PostTypes } from '@domain/post'; import { UserTypes } from '@domain/user'; const { VERSTKA_API_URL = 'https://verstka.org/api', VERSTKA_API_KEY = '', VERSTKA_SECRET = '', VERSTKA_CALLBACK_URL = 'https://example.com/api/verstka', VERSTKA_HOST_NAME = 'storage.yandexcloud.net/imagesexample.com', VERSTKA_FONTS = 'https://storage.yandexcloud.net/imagesexample.com/vms

TypeScript
View
translateMon, 27 Feb 2023

import { Inject } from '@nestjs/common'; import { createHash } from 'crypto'; import axios, { AxiosError } from 'axios'; import { strict as assert } from 'assert'; import { PostRepository } from '@app/post'; import { UserRepository } from '@app/user'; import { AuthUser } from '@app/auth'; import { Post, PostTypes } from '@domain/post'; import { UserTypes } from '@domain/user'; const { VERSTKA_API_URL = 'https://verstka.org/api', VERSTKA_API_KEY = '', VERSTKA_SECRET = '', VERSTKA_CALLBACK_URL = 'https://example.com/api/verstka', VERSTKA_HOST_NAME = 'storage.yandexcloud.net/imagesexample.com', VERSTKA_FONTS = 'https://storage.yandexcloud.net/imagesexample.com/vms_fonts.css', IMAGE_SERVICE_ENDPOINT = 'https://images.example.com', } = process.env; type EditParams = { id: string; userId: string; ip: string; isMobile?: boolean; }; type SaveParams = { id: string; html: string; userId: string; downloadUrl: string; customFields: { client_folder: string; mobile?: boolean; }; }; type EditResponse = | string | { data: { session_id: string; edit_url: string; }; }; type VerstkaPostExtraFields = { imagesMap?: Record<string, string>; }; export class VerstkaService { @Inject() private readonly postRepository: PostRepository; @Inject() private readonly userRepository: UserRepository; public async getList(): Promise<Post[]> { return this.postRepository.find({ type: PostTypes.Type.VERSTKA }); } public async edit({ id, ip, userId, isMobile }: EditParams): Promise<never | string> { const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, }; try { const data = new URLSearchParams(); data.append('material_id', materialId); data.append('user_id', userId); data.append('html_body', html); data.append('api-key', VERSTKA_API_KEY); data.append('callback_url', callbackUrl); data.append('host_name', VERSTKA_HOST_NAME); data.append('user_ip', ip); data.append('callback_sign', callbackSign); data.append('custom_fields', JSON.stringify(customFields)); const response = await axios(`${VERSTKA_API_URL}/open`, { data, method: 'post', }); const verstkaRespose = response.data as EditResponse; assert.ok(typeof verstkaRespose !== 'string', verstkaRespose as string); return verstkaRespose.data.edit_url; } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } } public async save(params: SaveParams): Promise<void> { const isMobile = params.customFields.mobile; const id = isMobile ? params.id.substring(1) : params.id; const post = await this.postRepository.getOrFail(id); const html = await this.downloadFiles(params.html, params.downloadUrl, post); const user = await this.getUser(params.userId); await this.updateContent(post, html, user, isMobile); } private async downloadFiles(html: string, downloadUrl: string, post: Post): Promise<string> { let result = html; const fields = (post.extraFields ?? {}) as VerstkaPostExtraFields; const imagesMap = fields.imagesMap ?? {}; const { data } = await axios.get(downloadUrl); const files = (data?.data ?? []) as string[]; let url: Optional<string>; let originalUrl: string; for (const file of files) { originalUrl = `${downloadUrl}/${file}`; url = imagesMap[originalUrl]; if (!url) { url = await this.downloadFile(originalUrl); imagesMap[originalUrl] = url; } result = result.replaceAll(`/vms_images/${file}`, url); } post.extraFields = { imagesMap }; return result; } private async getUser(userId: UserTypes.Id): Promise<AuthUser> { const user = await this.userRepository.getOrFail(userId); const { id, role } = user.toDTO(); return new AuthUser(id, role); } private async downloadFile(url: string): Promise<string> { let result: string; try { const source = encodeURI(url); // eslint-disable-next-line no-console console.time(`Upload ${url}`); const response = await axios.post( `${IMAGE_SERVICE_ENDPOINT}/image?defaultPreviews=1&copyFromUrl=${source}&isPreserveTransparency=true` ); const { image } = response.data; // eslint-disable-next-line no-console console.timeEnd(`Upload ${url}`); result = image.urlTemplate.replace('{width}', 'original'); } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } return result; } // eslint-disable-next-line max-params private async updateContent(post: Post, body: string, user: AuthUser, isMobile?: boolean): Promise<void> { const pageHtml = post.content.body ?? ''; const desktopContent = isMobile ? this.htmlUnboxing(pageHtml) : body; const mobileContent = isMobile ? body : this.htmlUnboxing(pageHtml, true); const content = { body: this.htmlBoxing(desktopContent, mobileContent) }; post.update({ content }, user); await this.postRepository.update(post); } private logAxiosError(error: AxiosError): void { if (error.response) { // eslint-disable-next-line no-console console.error(error.response.data); // eslint-disable-next-line no-console console.error(error.response.status); // eslint-disable-next-line no-console console.error(error.response.headers); } else { // eslint-disable-next-line no-console console.error(error.message); } } // eslint-disable-next-line max-lines-per-function private htmlBoxing(desktopContent = '', mobileContent = ''): string { return ` <link rel="stylesheet" media="all" href="${VERSTKA_FONTS}" /> <article class="verstka-article"> <div class="content content_desktop">${desktopContent}</div> <div class="content content_mobile">${mobileContent}</div> </article> <script type = "text/javascript"> (() => { const [mobileContent] = document.getElementsByClassName('content_mobile'); const [desktopContent] = document.getElementsByClassName('content_desktop') const htmls = { desktop: desktopContent.outerHTML, mobile: mobileContent.innerHTML ? mobileContent.outerHTML : desktopContent.outerHTML, }; mobileContent.parentElement.removeChild(mobileContent); desktopContent.parentElement.removeChild(desktopContent); function switchHtml(html) { const [article] = document.getElementsByClassName('verstka-article'); if (window.VMS_API) { window.VMS_API.Article.disable() } article.innerHTML = html; if (window.VMS_API) { window.VMS_API.Article.enable({ display_mode: 'default' }); } } let prev = null; function onResize() { const isMobile = window.innerWidth < 768; if (prev !== isMobile) { prev = isMobile; switchHtml(htmls[isMobile ? 'mobile' : 'desktop']); } } window.onresize = onResize; if (window.VMS_API) { onResize(); } else { window.onVMSAPIReady = onResize; } })(); </script> <script src="https://go.verstka.org/api.js" async type="text/javascript"></script> `; } private htmlUnboxing(html: string, isMobile?: boolean): string { const desktopOpenTag = '<div class="content content_desktop">'; const mobileOpenTag = '<div class="content content_mobile">'; const closeTag = '</div>'; const desktopStart = html.indexOf(desktopOpenTag); const mobileStart = html.indexOf(mobileOpenTag); const desktopEnd = html.lastIndexOf(closeTag, mobileStart); const mobileEnd = html.lastIndexOf(closeTag); return isMobile ? html.substring(mobileStart + mobileOpenTag.length, mobileEnd) : html.substring(desktopStart + desktopOpenTag.length, desktopEnd); } }

def add(a, b) return a + b end

Ruby
View
translateMon, 27 Feb 2023

import { Inject } from '@nestjs/common'; import { createHash } from 'crypto'; import axios, { AxiosError } from 'axios'; import { strict as assert } from 'assert'; import { PostRepository } from '@app/post'; import { UserRepository } from '@app/user'; import { AuthUser } from '@app/auth'; import { Post, PostTypes } from '@domain/post'; import { UserTypes } from '@domain/user'; const { VERSTKA_API_URL = 'https://verstka.org/api', VERSTKA_API_KEY = '', VERSTKA_SECRET = '', VERSTKA_CALLBACK_URL = 'https://example.com/api/verstka', VERSTKA_HOST_NAME = 'storage.yandexcloud.net/imagesexample.com', VERSTKA_FONTS = 'https://storage.yandexcloud.net/imagesexample.com/vms_fonts.css', IMAGE_SERVICE_ENDPOINT = 'https://images.example.com', } = process.env; type EditParams = { id: string; userId: string; ip: string; isMobile?: boolean; }; type SaveParams = { id: string; html: string; userId: string; downloadUrl: string; customFields: { client_folder: string; mobile?: boolean; }; }; type EditResponse = | string | { data: { session_id: string; edit_url: string; }; }; type VerstkaPostExtraFields = { imagesMap?: Record<string, string>; }; export class VerstkaService { @Inject() private readonly postRepository: PostRepository; @Inject() private readonly userRepository: UserRepository; public async getList(): Promise<Post[]> { return this.postRepository.find({ type: PostTypes.Type.VERSTKA }); } public async edit({ id, ip, userId, isMobile }: EditParams): Promise<never | string> { const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, }; try { const data = new URLSearchParams(); data.append('material_id', materialId); data.append('user_id', userId); data.append('html_body', html); data.append('api-key', VERSTKA_API_KEY); data.append('callback_url', callbackUrl); data.append('host_name', VERSTKA_HOST_NAME); data.append('user_ip', ip); data.append('callback_sign', callbackSign); data.append('custom_fields', JSON.stringify(customFields)); const response = await axios(`${VERSTKA_API_URL}/open`, { data, method: 'post', }); const verstkaRespose = response.data as EditResponse; assert.ok(typeof verstkaRespose !== 'string', verstkaRespose as string); return verstkaRespose.data.edit_url; } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } } public async save(params: SaveParams): Promise<void> { const isMobile = params.customFields.mobile; const id = isMobile ? params.id.substring(1) : params.id; const post = await this.postRepository.getOrFail(id); const html = await this.downloadFiles(params.html, params.downloadUrl, post); const user = await this.getUser(params.userId); await this.updateContent(post, html, user, isMobile); } private async downloadFiles(html: string, downloadUrl: string, post: Post): Promise<string> { let result = html; const fields = (post.extraFields ?? {}) as VerstkaPostExtraFields; const imagesMap = fields.imagesMap ?? {}; const { data } = await axios.get(downloadUrl); const files = (data?.data ?? []) as string[]; let url: Optional<string>; let originalUrl: string; for (const file of files) { originalUrl = `${downloadUrl}/${file}`; url = imagesMap[originalUrl]; if (!url) { url = await this.downloadFile(originalUrl); imagesMap[originalUrl] = url; } result = result.replaceAll(`/vms_images/${file}`, url); } post.extraFields = { imagesMap }; return result; } private async getUser(userId: UserTypes.Id): Promise<AuthUser> { const user = await this.userRepository.getOrFail(userId); const { id, role } = user.toDTO(); return new AuthUser(id, role); } private async downloadFile(url: string): Promise<string> { let result: string; try { const source = encodeURI(url); // eslint-disable-next-line no-console console.time(`Upload ${url}`); const response = await axios.post( `${IMAGE_SERVICE_ENDPOINT}/image?defaultPreviews=1&copyFromUrl=${source}&isPreserveTransparency=true` ); const { image } = response.data; // eslint-disable-next-line no-console console.timeEnd(`Upload ${url}`); result = image.urlTemplate.replace('{width}', 'original'); } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } return result; } // eslint-disable-next-line max-params private async updateContent(post: Post, body: string, user: AuthUser, isMobile?: boolean): Promise<void> { const pageHtml = post.content.body ?? ''; const desktopContent = isMobile ? this.htmlUnboxing(pageHtml) : body; const mobileContent = isMobile ? body : this.htmlUnboxing(pageHtml, true); const content = { body: this.htmlBoxing(desktopContent, mobileContent) }; post.update({ content }, user); await this.postRepository.update(post); } private logAxiosError(error: AxiosError): void { if (error.response) { // eslint-disable-next-line no-console console.error(error.response.data); // eslint-disable-next-line no-console console.error(error.response.status); // eslint-disable-next-line no-console console.error(error.response.headers); } else { // eslint-disable-next-line no-console console.error(error.message); } } // eslint-disable-next-line max-lines-per-function private htmlBoxing(desktopContent = '', mobileContent = ''): string { return ` <link rel="stylesheet" media="all" href="${VERSTKA_FONTS}" /> <article class="verstka-article"> <div class="content content_desktop">${desktopContent}</div> <div class="content content_mobile">${mobileContent}</div> </article> <script type = "text/javascript"> (() => { const [mobileContent] = document.getElementsByClassName('content_mobile'); const [desktopContent] = document.getElementsByClassName('content_desktop') const htmls = { desktop: desktopContent.outerHTML, mobile: mobileContent.innerHTML ? mobileContent.outerHTML : desktopContent.outerHTML, }; mobileContent.parentElement.removeChild(mobileContent); desktopContent.parentElement.removeChild(desktopContent); function switchHtml(html) { const [article] = document.getElementsByClassName('verstka-article'); if (window.VMS_API) { window.VMS_API.Article.disable() } article.innerHTML = html; if (window.VMS_API) { window.VMS_API.Article.enable({ display_mode: 'default' }); } } let prev = null; function onResize() { const isMobile = window.innerWidth < 768; if (prev !== isMobile) { prev = isMobile; switchHtml(htmls[isMobile ? 'mobile' : 'desktop']); } } window.onresize = onResize; if (window.VMS_API) { onResize(); } else { window.onVMSAPIReady = onResize; } })(); </script> <script src="https://go.verstka.org/api.js" async type="text/javascript"></script> `; } private htmlUnboxing(html: string, isMobile?: boolean): string { const desktopOpenTag = '<div class="content content_desktop">'; const mobileOpenTag = '<div class="content content_mobile">'; const closeTag = '</div>'; const desktopStart = html.indexOf(desktopOpenTag); const mobileStart = html.indexOf(mobileOpenTag); const desktopEnd = html.lastIndexOf(closeTag, mobileStart); const mobileEnd = html.lastIndexOf(closeTag); return isMobile ? html.substring(mobileStart + mobileOpenTag.length, mobileEnd) : html.substring(desktopStart + desktopOpenTag.length, desktopEnd); } }

def add(a, b) return a + b end

Ruby
View
fix invalid codeWed, 15 Feb 2023

gem 'addressable', '2.8.0' gem 'css_parser', '1.6.0' gem 'ffi', '1.12.2' gem 'concurrent-ruby', '1.1.9' gem 'dry-inflector', '0.1.2' gem 'faraday', '0.9.2' gem 'multipart-post', '2.0.0' gem 'newrelic_rpm', '5.0.0.342' # last version of newrelic_rpm not work, on wonder we use newrelic_rpm (5.0.0.342) gem 'pundit', '1.1.0' gem 'rake', '12.3.3' # gem 'premailer', '1.11.1' gem 'i18n-js', '3.8.3' gem 'jwt', '2.2.1' # may update to 2.2.3 (for ruby 2.1.5) gem 'sprockets-rails', '3.2.2' gem 'sprockets', '3.7.2' gem 'minitest', '5.10.3' gem 'public_suffix', '3.0.3' gem 'unf_ext', '0.0.7.7' # 0.0.8 drops Ruby 2.1 and earlier. gem 'yajl-ruby', '1.4.1' # drop ruby < 2.3 gem 'rest-client', '2.0.0'

### Run rails

translateMon, 27 Feb 2023

public async edit({ id, ip, userId, isMobile }: EditParams): Promise<never | string> { const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, }; try { const data = new URLSearchParams(); data.append('material_id', materialId); data.append('user_id', userId); data.append('html_body', html); data.append('api-key', VERSTKA_API_KEY); data.append('callback_url', callbackUrl); data.append('host_name', VERSTKA_HOST_NAME); data.append('user_ip', ip); data.append('callback_sign', callbackSign); data.append('custom_fields', JSON.stringify(customFields)); const response = await axios(`${VERSTKA_API_URL}/open`, { data, method: 'post', }); const verstkaRespose = response.data as EditResponse; assert.ok(typeof verstkaRespose !== 'string', verstkaRespose as string); return verstkaRespose.data.edit_url; } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } }

def add(a, b) return a + b end

Ruby
View
time complexityMon, 06 Mar 2023

def body_splited_by_paywall return body if paywall_placeholder.blank? return body unless body.include?(paywall_placeholder) Nokogiri::HTML.fragment(%( #{body.split(paywall_placeholder)[0]} #{Grid::Core::Post::PAYWALL_PLACEHOLDER} )).to_html end

O(1) -------------------------- time complexity of the code: def body_with_paywall_placeholder return body unless body.include?(Grid::Core::Post::PAYWALL_PLACEHOLDER) Nokogiri::HTML.fragment(%( #{body.split(Grid::Core::Post::PAYWALL_PLACEHOLDER)[0]} <span class="paywall-placeholder">This is paywalled content</span> )).to_html end

generate functionMon, 06 Mar 2023

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg> generate template with this svg custom color for circle with rgb(74, 0, 255)

require "erb" class Svg < ERB def initialize(filename) @filename = filename end def render(custom_color) ERB.new(File.read(@filename)).result(binding) end end custom_color = "rgb(74, 0, 255)" puts Svg.new("./my_svg.erb").render(custom_color)

Ruby
View
generate functionMon, 06 Mar 2023

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg> color this svg circle with rgb(74, 0, 255)

class RgbColor { constructor(red: number, green: number, blue: number) { this.red = red; this.green = green; this.blue = blue; } public red: number; public green: number; public blue: number; } const color = new RgbColor(74, 0, 255);

TypeScript
View
translateMon, 27 Feb 2023

import { Inject } from '@nestjs/common'; import { createHash } from 'crypto'; import axios, { AxiosError } from 'axios'; import { strict as assert } from 'assert'; import { PostRepository } from '@app/post'; import { UserRepository } from '@app/user'; import { AuthUser } from '@app/auth'; import { Post, PostTypes } from '@domain/post'; import { UserTypes } from '@domain/user'; const { VERSTKA_API_URL = 'https://verstka.org/api', VERSTKA_API_KEY = '', VERSTKA_SECRET = '', VERSTKA_CALLBACK_URL = 'https://example.com/api/verstka', VERSTKA_HOST_NAME = 'storage.yandexcloud.net/imagesexample.com', VERSTKA_FONTS = 'https://storage.yandexcloud.net/imagesexample.com/vms_fonts.css', IMAGE_SERVICE_ENDPOINT = 'https://images.example.com', } = process.env; type EditParams = { id: string; userId: string; ip: string; isMobile?: boolean; }; type SaveParams = { id: string; html: string; userId: string; downloadUrl: string; customFields: { client_folder: string; mobile?: boolean; }; }; type EditResponse = | string | { data: { session_id: string; edit_url: string; }; }; type VerstkaPostExtraFields = { imagesMap?: Record<string, string>; }; export class VerstkaService { @Inject() private readonly postRepository: PostRepository; @Inject() private readonly userRepository: UserRepository; public async getList(): Promise<Post[]> { return this.postRepository.find({ type: PostTypes.Type.VERSTKA }); } public async edit({ id, ip, userId, isMobile }: EditParams): Promise<never | string> { const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, }; try { const data = new URLSearchParams(); data.append('material_id', materialId); data.append('user_id', userId); data.append('html_body', html); data.append('api-key', VERSTKA_API_KEY); data.append('callback_url', callbackUrl); data.append('host_name', VERSTKA_HOST_NAME); data.append('user_ip', ip); data.append('callback_sign', callbackSign); data.append('custom_fields', JSON.stringify(customFields)); const response = await axios(`${VERSTKA_API_URL}/open`, { data, method: 'post', }); const verstkaRespose = response.data as EditResponse; assert.ok(typeof verstkaRespose !== 'string', verstkaRespose as string); return verstkaRespose.data.edit_url; } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } } public async save(params: SaveParams): Promise<void> { const isMobile = params.customFields.mobile; const id = isMobile ? params.id.substring(1) : params.id; const post = await this.postRepository.getOrFail(id); const html = await this.downloadFiles(params.html, params.downloadUrl, post); const user = await this.getUser(params.userId); await this.updateContent(post, html, user, isMobile); } private async downloadFiles(html: string, downloadUrl: string, post: Post): Promise<string> { let result = html; const fields = (post.extraFields ?? {}) as VerstkaPostExtraFields; const imagesMap = fields.imagesMap ?? {}; const { data } = await axios.get(downloadUrl); const files = (data?.data ?? []) as string[]; let url: Optional<string>; let originalUrl: string; for (const file of files) { originalUrl = `${downloadUrl}/${file}`; url = imagesMap[originalUrl]; if (!url) { url = await this.downloadFile(originalUrl); imagesMap[originalUrl] = url; } result = result.replaceAll(`/vms_images/${file}`, url); } post.extraFields = { imagesMap }; return result; } private async getUser(userId: UserTypes.Id): Promise<AuthUser> { const user = await this.userRepository.getOrFail(userId); const { id, role } = user.toDTO(); return new AuthUser(id, role); } private async downloadFile(url: string): Promise<string> { let result: string; try { const source = encodeURI(url); // eslint-disable-next-line no-console console.time(`Upload ${url}`); const response = await axios.post( `${IMAGE_SERVICE_ENDPOINT}/image?defaultPreviews=1&copyFromUrl=${source}&isPreserveTransparency=true` ); const { image } = response.data; // eslint-disable-next-line no-console console.timeEnd(`Upload ${url}`); result = image.urlTemplate.replace('{width}', 'original'); } catch (error: unknown) { this.logAxiosError(error as AxiosError); throw error; } return result; } // eslint-disable-next-line max-params private async updateContent(post: Post, body: string, user: AuthUser, isMobile?: boolean): Promise<void> { const pageHtml = post.content.body ?? ''; const desktopContent = isMobile ? this.htmlUnboxing(pageHtml) : body; const mobileContent = isMobile ? body : this.htmlUnboxing(pageHtml, true); const content = { body: this.htmlBoxing(desktopContent, mobileContent) }; post.update({ content }, user); await this.postRepository.update(post); } private logAxiosError(error: AxiosError): void { if (error.response) { // eslint-disable-next-line no-console console.error(error.response.data); // eslint-disable-next-line no-console console.error(error.response.status); // eslint-disable-next-line no-console console.error(error.response.headers); } else { // eslint-disable-next-line no-console console.error(error.message); } } // eslint-disable-next-line max-lines-per-function private htmlBoxing(desktopContent = '', mobileContent = ''): string { return ` <link rel="stylesheet" media="all" href="${VERSTKA_FONTS}" /> <article class="verstka-article"> <div class="content content_desktop">${desktopContent}</div> <div class="content content_mobile">${mobileContent}</div> </article> <script type = "text/javascript"> (() => { const [mobileContent] = document.getElementsByClassName('content_mobile'); const [desktopContent] = document.getElementsByClassName('content_desktop') const htmls = { desktop: desktopContent.outerHTML, mobile: mobileContent.innerHTML ? mobileContent.outerHTML : desktopContent.outerHTML, }; mobileContent.parentElement.removeChild(mobileContent); desktopContent.parentElement.removeChild(desktopContent); function switchHtml(html) { const [article] = document.getElementsByClassName('verstka-article'); if (window.VMS_API) { window.VMS_API.Article.disable() } article.innerHTML = html; if (window.VMS_API) { window.VMS_API.Article.enable({ display_mode: 'default' }); } } let prev = null; function onResize() { const isMobile = window.innerWidth < 768; if (prev !== isMobile) { prev = isMobile; switchHtml(htmls[isMobile ? 'mobile' : 'desktop']); } } window.onresize = onResize; if (window.VMS_API) { onResize(); } else { window.onVMSAPIReady = onResize; } })(); </script> <script src="https://go.verstka.org/api.js" async type="text/javascript"></script> `; } private htmlUnboxing(html: string, isMobile?: boolean): string { const desktopOpenTag = '<div class="content content_desktop">'; const mobileOpenTag = '<div class="content content_mobile">'; const closeTag = '</div>'; const desktopStart = html.indexOf(desktopOpenTag); const mobileStart = html.indexOf(mobileOpenTag); const desktopEnd = html.lastIndexOf(closeTag, mobileStart); const mobileEnd = html.lastIndexOf(closeTag); return isMobile ? html.substring(mobileStart + mobileOpenTag.length, mobileEnd) : html.substring(desktopStart + desktopOpenTag.length, desktopEnd); } }

def add(a, b): return a + b

Ruby
View
generate functionMon, 06 Mar 2023

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg> color this svg circle with rgb(74, 0, 255)

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg>

Ruby
View
translateMon, 27 Feb 2023

const user = await this.getUser(userId); user.assert(UserTypes.Permission.WriteArticle); const post = await this.postRepository.getOrFail(id); const html = post.content.body ? this.htmlUnboxing(post.content.body, isMobile) : ''; const materialId = isMobile ? `M${id}` : id; const callbackUrl = `${VERSTKA_CALLBACK_URL}/${id}`; const callbackSign = createHash('md5') .update(`${VERSTKA_SECRET}${VERSTKA_API_KEY}${materialId}${userId}${callbackUrl}`) .digest('hex'); const customFields = { mobile: isMobile, 'fonts.css': VERSTKA_FONTS, };

def add(a, b) return a + b end

Ruby
View
generate functionMon, 06 Mar 2023

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="#F34910"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg> generate template with this svg custom color for circle with rgb(74, 0, 255)

<svg viewBox="0 0 29 29" {...props}> <g fillRule="evenodd"> <circle cx="14.5" cy="14.5" r="14.5" fill="rgb(74, 0, 255)"/> <path fill="#C33D11" d="M13 9h3v11h-3z"/> <path fill="#C33D11" d="M20 13v3H9v-3z"/> </g> </svg>

Ruby
View

Questions about programming?Chat with your personal AI assistant