<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ayumi Sato</title>
    <description>The latest articles on DEV Community by Ayumi Sato (@ayumisato).</description>
    <link>https://dev.to/ayumisato</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F907028%2F49b1711d-35d6-4db3-b0fd-8cc97f5616bb.jpeg</url>
      <title>DEV Community: Ayumi Sato</title>
      <link>https://dev.to/ayumisato</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ayumisato"/>
    <language>en</language>
    <item>
      <title>Importing Toots from Mastodon to WordPress Posts Using Google Apps Script</title>
      <dc:creator>Ayumi Sato</dc:creator>
      <pubDate>Fri, 26 Jul 2024 05:05:45 +0000</pubDate>
      <link>https://dev.to/ayumisato/importing-toots-from-mastodon-to-wordpress-posts-using-google-apps-script-24mg</link>
      <guid>https://dev.to/ayumisato/importing-toots-from-mastodon-to-wordpress-posts-using-google-apps-script-24mg</guid>
      <description>&lt;p&gt;I have been archiving my online writings on a private WordPress site since 2003. This includes diaries originally written in HTML, posts from other external platforms, and content created using MovableType - all of which have been migrated to WordPress. Although I don't post regularly and not everything is archived, I still have an impressive collection spanning 15 years, totaling approximately 2,500 articles.&lt;br&gt;
Recently, I wanted to import my past toots from Mastodon into WordPress in a similar manner. I was able to achieve this using Google Apps Script. By importing my toots into my WordPress environment, I can effectively manage my data and easily search through the content.&lt;br&gt;
If you are looking to do something similar with your own Mastodon toots and WordPress site, I hope this article will be a helpful resource. &lt;/p&gt;
&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Import past toots from my Mastodon account into WordPress.&lt;/li&gt;
&lt;li&gt;Use Google Apps Script to avoid setting up a new environment.&lt;/li&gt;
&lt;li&gt;Set the toot’s post date as the article’s post date in WordPress.&lt;/li&gt;
&lt;li&gt;Use the first 20 characters of the toot’s body as the WordPress article title.&lt;/li&gt;
&lt;li&gt;If the toot’s body is empty, set the WordPress article title as "no title".&lt;/li&gt;
&lt;li&gt;Categorize the articles as "Toots".&lt;/li&gt;
&lt;li&gt;Attach images from toots to the WordPress article body.&lt;/li&gt;
&lt;li&gt;Convert hashtags in toots to WordPress tags.&lt;/li&gt;
&lt;li&gt;Exclude boosts and replies.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What Was Not Done
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Video attachments.&lt;/li&gt;
&lt;li&gt;Other necessary or unnecessary things I didn't think of.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;To fetch data from Mastodon, you need an access token.&lt;/p&gt;
&lt;h3&gt;
  
  
  Obtaining the Mastodon Access Token
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your Mastodon instance.&lt;/li&gt;
&lt;li&gt;Select "Development" from "User Settings" and click "New Application".&lt;/li&gt;
&lt;li&gt;Enter an application name and select the necessary permissions (scope). For this task, "read" permission is sufficient.&lt;/li&gt;
&lt;li&gt;Click "Submit".&lt;/li&gt;
&lt;li&gt;Note the access token from the application details page.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Google Apps Script Code
&lt;/h2&gt;

&lt;p&gt;Write the following code in Google Apps Script. Set the URL of the Mastodon instance, the obtained access token, and the URL of the WordPress site.&lt;br&gt;
Execute the main() function. It may take a few minutes to complete. If a security warning appears during execution, please allow it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Main function: Convert Mastodon posts to WordPress format and save as an XML file
function main() {
  const instanceUrl = "https://mastodon.example.com"; // Set the URL of the Mastodon instance
  const accessToken = "your_token_here"; // Set the obtained access token
  const siteUrl = "https://wordpress.example.com"; // Set the URL of the WordPress site

  // Fetch Mastodon posts
  const posts = fetchMastodonPosts(instanceUrl, accessToken);

  // Convert fetched posts to WordPress format XML
  const wxrContent = createWxrFile(posts, siteUrl);

  // Create and save the XML file to Google Drive
  const blob = Utilities.newBlob(wxrContent, "application/xml", "mastodon_posts.xml");
  const file = DriveApp.createFile(blob);
  Logger.log("File created: " + file.getUrl());
}

// Function to fetch Mastodon posts
function fetchMastodonPosts(instanceUrl, accessToken) {
  const headers = { Authorization: "Bearer " + accessToken };
  const options = { method: "get", headers: headers };

  // Get user ID
  const userId = getUserId(instanceUrl, options);

  // Fetch all posts
  return fetchAllPosts(instanceUrl, userId, options);
}

// Function to get user ID
function getUserId(instanceUrl, options) {
  const userResponse = UrlFetchApp.fetch(`${instanceUrl}/api/v1/accounts/verify_credentials`, options);
  return JSON.parse(userResponse.getContentText()).id;
}

// Function to fetch all posts
function fetchAllPosts(instanceUrl, userId, options) {
  let posts = [];
  let url = `${instanceUrl}/api/v1/accounts/${userId}/statuses`;

  while (url) {
    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());
    posts = posts.concat(data);

    // Get the URL of the next page
    url = getNextPageUrl(response);
  }

  return posts;
}

// Function to get the URL of the next page from response headers
function getNextPageUrl(response) {
  const links = response.getHeaders()["Link"];
  if (links &amp;amp;&amp;amp; links.includes('rel="next"')) {
    return links.match(/&amp;lt;(.*)&amp;gt;; rel="next"/)[1];
  }
  return null;
}

// Function to strip HTML tags
function stripHtmlTags(str) {
  if (!str) return "";
  return str.toString().replace(/&amp;lt;[^&amp;gt;]*&amp;gt;/g, "");
}

// Function to convert HTML content to WordPress blocks
function convertToWordPressBlocks(htmlContent) {
  return htmlContent
    .replace(/&amp;lt;p&amp;gt;(.*?)&amp;lt;\/p&amp;gt;/g, (match, content) =&amp;gt; `&amp;lt;!-- wp:paragraph --&amp;gt;\n&amp;lt;p&amp;gt;${content}&amp;lt;/p&amp;gt;\n&amp;lt;!-- /wp:paragraph --&amp;gt;\n`)
    .replace(/&amp;lt;img src="(.*?)" alt="(.*?)" \/&amp;gt;/g, (match, src, alt) =&amp;gt; `&amp;lt;!-- wp:image --&amp;gt;\n&amp;lt;figure class="wp-block-image"&amp;gt;&amp;lt;img src="${src}" alt="${alt}" /&amp;gt;&amp;lt;/figure&amp;gt;\n&amp;lt;!-- /wp:image --&amp;gt;\n`);
}

// Function to format date for RSS
function formatPubDate(date) {
  const myDate = new Date(date);
  const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  return `${days[myDate.getUTCDay()]}, ${myDate.getUTCDate().toString().padStart(2, "0")} ${months[myDate.getUTCMonth()]} ${myDate.getUTCFullYear()} ${myDate.getUTCHours().toString().padStart(2, "0")}:${myDate.getUTCMinutes().toString().padStart(2, "0")}:${myDate.getUTCSeconds().toString().padStart(2, "0")} +0000`;
}

// Function to format date for WordPress
function formatDateToWordPress(date) {
  const myDate = new Date(date);
  return `${myDate.getFullYear()}-${String(myDate.getMonth() + 1).padStart(2, "0")}-${String(myDate.getDate()).padStart(2, "0")} ${String(myDate.getHours()).padStart(2, "0")}:${String(myDate.getMinutes()).padStart(2, "0")}:${String(myDate.getSeconds()).padStart(2, "0")}`;
}

// Function to create WordPress eXtended RSS (WXR) file
function createWxrFile(posts, siteUrl) {
  let xml = '&amp;lt;?xml version="1.0" encoding="UTF-8" ?&amp;gt;\n';
  xml += '&amp;lt;rss version="2.0" xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wp="http://wordpress.org/export/1.2/"&amp;gt;\n';
  xml += "&amp;lt;channel&amp;gt;\n";
  xml += "&amp;lt;wp:wxr_version&amp;gt;1.2&amp;lt;/wp:wxr_version&amp;gt;\n";
  posts.forEach((post, index) =&amp;gt; {
    xml += createWxrItem(post, index, siteUrl);
  });
  xml += "&amp;lt;/channel&amp;gt;\n";
  xml += "&amp;lt;/rss&amp;gt;\n";
  return xml;
}

// Function to create WXR item for each post
function createWxrItem(post, index, siteUrl) {
  // Skip replies and reblogs
  if (post.in_reply_to_id !== null || post.reblog !== null) {
    return "";
  }

  let content = post.content;
  const strippedContent = stripHtmlTags(post.content);
  const title = post.spoiler_text ? `⚠️${post.spoiler_text}` : post.content ? strippedContent.substring(0, 20) : "no title";
  const postDate = formatDateToWordPress(post.created_at);
  const postPubDate = formatPubDate(post.created_at);

  // Extract hashtags
  const hashtags = extractHashtags(content);

  // Convert hashtags to links
  content = convertHashtagsToLinks(content, siteUrl);

  // Add spoiler text if present
  if (post.spoiler_text) {
    content = `&amp;lt;p&amp;gt;${post.spoiler_text}&amp;lt;/p&amp;gt;${content}`;
  }

  // Add media attachments if present
  if (post.media_attachments.length &amp;gt; 0) {
    post.media_attachments.forEach((media) =&amp;gt; {
      const alt = media.description ? media.description : "";
      if (media.type === "image") {
        content += `\n\n&amp;lt;img src="${media.url}" alt="${alt}" /&amp;gt;`;
      }
    });
  }

  // Construct WXR item
  let xmlItem = `
&amp;lt;item&amp;gt;
&amp;lt;title&amp;gt;&amp;lt;![CDATA[${title}]]&amp;gt;&amp;lt;/title&amp;gt;
&amp;lt;content:encoded&amp;gt;&amp;lt;![CDATA[${convertToWordPressBlocks(content)}]]&amp;gt;&amp;lt;/content:encoded&amp;gt;
&amp;lt;excerpt:encoded&amp;gt;&amp;lt;![CDATA[]]&amp;gt;&amp;lt;/excerpt:encoded&amp;gt;
&amp;lt;pubDate&amp;gt;&amp;lt;![CDATA[${postPubDate}]]&amp;gt;&amp;lt;/pubDate&amp;gt;
&amp;lt;dc:creator&amp;gt;&amp;lt;![CDATA[dummy]]&amp;gt;&amp;lt;/dc:creator&amp;gt;
&amp;lt;wp:post_id&amp;gt;${index + 1}&amp;lt;/wp:post_id&amp;gt;
&amp;lt;wp:post_date&amp;gt;&amp;lt;![CDATA[${postDate}]]&amp;gt;&amp;lt;/wp:post_date&amp;gt;
&amp;lt;wp:post_date_gmt&amp;gt;&amp;lt;![CDATA[${postDate}]]&amp;gt;&amp;lt;/wp:post_date_gmt&amp;gt;
&amp;lt;wp:post_modified&amp;gt;&amp;lt;![CDATA[${postDate}]]&amp;gt;&amp;lt;/wp:post_modified&amp;gt;
&amp;lt;wp:post_modified_gmt&amp;gt;&amp;lt;![CDATA[${postDate}]]&amp;gt;&amp;lt;/wp:post_modified_gmt&amp;gt;
&amp;lt;wp:post_type&amp;gt;post&amp;lt;/wp:post_type&amp;gt;
&amp;lt;wp:status&amp;gt;&amp;lt;![CDATA[publish]]&amp;gt;&amp;lt;/wp:status&amp;gt;
&amp;lt;category domain="category" nicename="toots"&amp;gt;&amp;lt;![CDATA[Toots]]&amp;gt;&amp;lt;/category&amp;gt;
`;

  // Add hashtags as WordPress tags
  hashtags.forEach((tag) =&amp;gt; {
    xmlItem += ` &amp;lt;category domain="post_tag" nicename="${tag}"&amp;gt;&amp;lt;![CDATA[${tag}]]&amp;gt;&amp;lt;/category&amp;gt;\n`;
  });

  xmlItem += " &amp;lt;/item&amp;gt;\n";
  return xmlItem;
}

// Function to extract hashtags from content
function extractHashtags(content) {
  const regex = /&amp;lt;a href="[^"]*" class="mention hashtag" rel="tag"&amp;gt;#&amp;lt;span&amp;gt;([^&amp;lt;]+)&amp;lt;\/span&amp;gt;&amp;lt;\/a&amp;gt;/g;
  const hashtags = [];
  let match;
  while ((match = regex.exec(content)) !== null) {
    hashtags.push(match[1]);
  }
  return hashtags;
}

// Function to convert hashtags to WordPress links
function convertHashtagsToLinks(content, siteUrl) {
  return content.replace(/&amp;lt;a href="[^"]*" class="mention hashtag" rel="tag"&amp;gt;#&amp;lt;span&amp;gt;([^&amp;lt;]+)&amp;lt;\/span&amp;gt;&amp;lt;\/a&amp;gt;/g, function (match, tag) {
    const tagUrl = `${siteUrl}/tag/${encodeURIComponent(tag)}/`;
    return `&amp;lt;a href="${tagUrl}" class="hashtag"&amp;gt;#${tag}&amp;lt;/a&amp;gt;`;
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Download the WXR File
&lt;/h2&gt;

&lt;p&gt;After running the script, an XML file will be created in Google Drive. Download this file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6r5r781ioavy12sj0nu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6r5r781ioavy12sj0nu0.png" alt="The image shows the Google Apps Script editor with a script named " width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Import into WordPress
&lt;/h2&gt;

&lt;p&gt;From the WordPress admin panel, go to "Tools" → "Import" and select "WordPress". Import the WXR file.&lt;br&gt;
Before importing into the production environment, be sure to verify that the import works correctly in a test environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Import External Images into WordPress
&lt;/h2&gt;

&lt;p&gt;Initially, images in imported articles are linked directly to the Mastodon instance. To import these images into WordPress, I used the &lt;a href="https://wordpress.org/plugins/auto-upload-images/" rel="noopener noreferrer"&gt;Auto Upload Images plugin&lt;/a&gt;. Although it seems outdated, I couldn't find another plugin with the same functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Auto Upload Images Plugin
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install and activate the plugin.&lt;/li&gt;
&lt;li&gt;From the WordPress admin panel, select "Tools" → "Replace External Images".&lt;/li&gt;
&lt;li&gt;Select the target articles from the post list.&lt;/li&gt;
&lt;li&gt;Click the "Replace" button to upload the images.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set Featured Images
&lt;/h3&gt;

&lt;p&gt;After importing images, set featured images as needed. To streamline this process, I used the &lt;a href="https://wordpress.org/plugins/xo-featured-image-tools/" rel="noopener noreferrer"&gt;XO Featured Image Tools plugin&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using XO Featured Image Tools
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Install and activate the plugin.&lt;/li&gt;
&lt;li&gt;From the WordPress admin panel, select "Tools" → "Featured Image".&lt;/li&gt;
&lt;li&gt;Select the target posts and click "Create Featured Image from Image".&lt;/li&gt;
&lt;li&gt;Featured images will be generated automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Impressions
&lt;/h2&gt;

&lt;p&gt;When I decided to migrate my posts, I searched for tools but couldn't find any that met my requirements. Some tools were outdated or didn't fully import toots as WordPress posts.&lt;br&gt;
Since no suitable tools were available, I decided to create my own script with the help of Perplexity. The most challenging part was generating a minimal WordPress eXtended RSS (WXR) file for the migration.&lt;br&gt;
I couldn't find the WXR specifications, so I exported articles from WordPress and wrote the script by mimicking the exported content.&lt;br&gt;
For now, the script works in my environment. Whether it will work in other environments or continue to work in the future is uncertain, but I plan to use it as needed.&lt;br&gt;
Next, I plan to create a script using Google Apps Script to continuously import new posts from Mastodon.&lt;/p&gt;

</description>
      <category>mastodon</category>
      <category>wordpress</category>
      <category>gas</category>
    </item>
    <item>
      <title>Official &amp; Unofficial STUDIO Keyboard Shortcuts List</title>
      <dc:creator>Ayumi Sato</dc:creator>
      <pubDate>Sun, 14 Aug 2022 06:16:00 +0000</pubDate>
      <link>https://dev.to/ayumisato/official-unofficial-studio-keyboard-shortcuts-list-ejd</link>
      <guid>https://dev.to/ayumisato/official-unofficial-studio-keyboard-shortcuts-list-ejd</guid>
      <description>&lt;p&gt;You can use some keyboard shortcuts on the &lt;a href="https://studio.design/"&gt;STUDIO&lt;/a&gt;, the online website builder. Keyboard shortcuts reduce mouse and trackpad operations, and you can build pages faster. This article introduces official shortcuts and also unofficial ones, which I have found.&lt;/p&gt;

&lt;p&gt;For Windows, replace ⌘ with Ctrl.&lt;/p&gt;

&lt;h2&gt;
  
  
  Officially announced shortcuts
&lt;/h2&gt;

&lt;p&gt;The shortcuts listed in the &lt;a href="https://help.studio.design/ja/articles/4062139-%E3%82%B7%E3%83%A7%E3%83%BC%E3%83%88%E3%82%AB%E3%83%83%E3%83%88%E4%B8%80%E8%A6%A7"&gt;STUDIO U (Japanese article)&lt;/a&gt; are as follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Box editing actions
&lt;/h3&gt;

&lt;p&gt;It works on the editor canvas or layer panel.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copy&lt;/td&gt;
&lt;td&gt;⌘ + C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cut&lt;/td&gt;
&lt;td&gt;⌘ + X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste&lt;/td&gt;
&lt;td&gt;⌘ + V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;td&gt;delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group&lt;/td&gt;
&lt;td&gt;⌘ + G&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ungroup&lt;/td&gt;
&lt;td&gt;⌘ + shift + G&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create list&lt;/td&gt;
&lt;td&gt;⌘ + L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create symbol&lt;/td&gt;
&lt;td&gt;⌘ + J&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Box selection actions
&lt;/h3&gt;

&lt;p&gt;It works when selecting some element on the editor canvas or layer panel.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Select multiple items&lt;/td&gt;
&lt;td&gt;shift + click&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select child box&lt;/td&gt;
&lt;td&gt;return (enter)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select parent box&lt;/td&gt;
&lt;td&gt;esc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select next box&lt;/td&gt;
&lt;td&gt;tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select previous box&lt;/td&gt;
&lt;td&gt;shift + tab&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By mastering selections, you can increase your work efficiency by applying the same state to multiple elements in a single operation. Also, selecting parent and child boxes from the keyboard allows you to go to the desired layer without opening and closing layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Editor action
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Undo&lt;/td&gt;
&lt;td&gt;⌘ + Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redo&lt;/td&gt;
&lt;td&gt;⌘ + shift + Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom in/Out&lt;/td&gt;
&lt;td&gt;+ / -&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View in 100%&lt;/td&gt;
&lt;td&gt;⌘ + 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable / Disable content editing mode&lt;/td&gt;
&lt;td&gt;⌘ + 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Undo shortcuts will let you return to the previous state instantly. In 100% view, the canvas will be displayed the same size as the actual website, reducing impression differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unofficial shortcuts
&lt;/h2&gt;

&lt;p&gt;The following shortcuts are not on the official shortcut list above, but I have confirmed that they work. I discovered these shortcuts by pressing keys that I thought might work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Box editing actions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate&lt;/td&gt;
&lt;td&gt;⌘ + D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate by D&amp;amp;D&lt;/td&gt;
&lt;td&gt;option (alt) + D&amp;amp;D&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Duplicate
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jYNC3MKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5o20yrqav76vfpsgn2yc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jYNC3MKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5o20yrqav76vfpsgn2yc.gif" alt="Duplicate" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Duplicate by drag&amp;amp;drop
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hckN5gSy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lc3281w3r0qcaqiaf94u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hckN5gSy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lc3281w3r0qcaqiaf94u.gif" alt="Duplicate by D&amp;amp;D" width="702" height="414"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Panel actions
&lt;/h3&gt;

&lt;p&gt;Opens/closes each panel.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open the Pages Panel&lt;/td&gt;
&lt;td&gt;⌘ + 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open the Layers Panel&lt;/td&gt;
&lt;td&gt;⌘ + 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open the Add Panel&lt;/td&gt;
&lt;td&gt;⌘ + 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open the Styles Panel&lt;/td&gt;
&lt;td&gt;⌘ + 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open the Box Settings Panel&lt;/td&gt;
&lt;td&gt;⌘ + 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IKt7c5x2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wp5qgn9dcuszc0szmz6a.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IKt7c5x2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wp5qgn9dcuszc0szmz6a.gif" alt="Panel actions" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer actions
&lt;/h3&gt;

&lt;p&gt;By clicking the layer toggle &amp;gt; with holding down the option key, it will open and close all the layers under it. Knowing this shortcut made all the difference in my working speed!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open/close all Layers&lt;/td&gt;
&lt;td&gt;option (alt) + click&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3qQGDE-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7flzqf2lifephdjekys1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3qQGDE-F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7flzqf2lifephdjekys1.gif" alt="Open/close all Layers" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Page actions
&lt;/h3&gt;

&lt;p&gt;Go back to the page that was opened before the page you are currently opening. These might be Google Chrome's shortcuts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Previous Page&lt;/td&gt;
&lt;td&gt;⌘ + ←&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next Page&lt;/td&gt;
&lt;td&gt;⌘ + →&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Richtext editing
&lt;/h3&gt;

&lt;p&gt;You can use these shortcuts in CMS article bodies and rich text boxes. In addition to the shortcuts below, you can use markdown as well.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Make the selection bold&lt;/td&gt;
&lt;td&gt;⌘ + B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italicize the selection&lt;/td&gt;
&lt;td&gt;⌘ + I&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Underline the selection&lt;/td&gt;
&lt;td&gt;⌘ + U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make the selection strikethrough&lt;/td&gt;
&lt;td&gt;⌘ + shift + S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add the link to the selection&lt;/td&gt;
&lt;td&gt;⌘ + K&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you know any other shortcuts, please let me know!&lt;/p&gt;

&lt;p&gt;Original Post: &lt;a href="https://zenn.dev/lima/articles/studio-keyboard-shortcuts"&gt;STUDIOの公式＆非公式キーボードショートカット一覧 (Japanese article)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>studio</category>
    </item>
  </channel>
</rss>
